mirror of
https://github.com/citizenfx/cfx-server-data.git
synced 2025-03-12 23:07:20 +08:00
Betterchat v1
This commit is contained in:
parent
aeab2edc5c
commit
ffa9488e9a
3
resources/[system]/chat/README.md
Normal file
3
resources/[system]/chat/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# betterchat
|
||||
|
||||
> Here will be some kind of documentation
|
@ -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'
|
||||
}
|
||||
|
@ -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)
|
@ -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)
|
85
resources/[system]/chat/cl_chat.lua
Normal file
85
resources/[system]/chat/cl_chat.lua
Normal file
@ -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)
|
166
resources/[system]/chat/html/App.js
Normal file
166
resources/[system]/chat/html/App.js
Normal file
@ -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,
|
||||
},
|
||||
}));
|
||||
};
|
43
resources/[system]/chat/html/Message.js
Normal file
43
resources/[system]/chat/html/Message.js
Normal file
@ -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, '"')
|
||||
.replace(/'/g, ''');
|
||||
},
|
||||
},
|
||||
props: {
|
||||
args: {
|
||||
|
||||
},
|
||||
template: {
|
||||
type: String,
|
||||
default: window.CONFIG.defaultTemplate,
|
||||
},
|
||||
multiline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
46
resources/[system]/chat/html/Suggestions.js
Normal file
46
resources/[system]/chat/html/Suggestions.js
Normal file
@ -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: {},
|
||||
};
|
@ -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;}
|
@ -1,20 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
||||
<script src="jquery.faketextbox.js" type="text/javascript"></script>
|
||||
<script src="chat.js" type="text/javascript"></script>
|
||||
<link href="chat.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat">
|
||||
<div id="chatBuffer">
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="chatInputHas">
|
||||
<strong>Chat</strong>
|
||||
<div id="chatInput" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,138 +0,0 @@
|
||||
function colorize(str)
|
||||
{
|
||||
const s = "<span>" + (str.replace(/\^([0-9])/g, (str, color) => `</span><span class="color-${color}">`)) + "</span>";
|
||||
return s.replace(/<span[^>]*><\/span[^>]*>/g, '');
|
||||
}
|
||||
|
||||
$(function()
|
||||
{
|
||||
var chatHideTimeout;
|
||||
var inputShown = false;
|
||||
|
||||
function startHideChat()
|
||||
{
|
||||
if (chatHideTimeout)
|
||||
{
|
||||
clearTimeout(chatHideTimeout);
|
||||
}
|
||||
|
||||
if (inputShown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chatHideTimeout = setTimeout(function()
|
||||
{
|
||||
if (inputShown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$('#chat').animate({ opacity: 0 }, 300);
|
||||
}, 7000);
|
||||
}
|
||||
|
||||
handleResult = function(elem, wasEnter)
|
||||
{
|
||||
inputShown = false;
|
||||
|
||||
$('#chatInputHas').hide();
|
||||
|
||||
startHideChat();
|
||||
|
||||
var obj = {};
|
||||
|
||||
if (wasEnter)
|
||||
{
|
||||
obj = { message: $(elem).val() };
|
||||
}
|
||||
|
||||
$(elem).val('');
|
||||
|
||||
$.post('http://chat/chatResult', JSON.stringify(obj), function(data)
|
||||
{
|
||||
console.log(data);
|
||||
});
|
||||
};
|
||||
|
||||
$('#chatInput').fakeTextbox(); // //
|
||||
|
||||
$('#chatInput')[0].onPress(function(e)
|
||||
{
|
||||
if (e.which == 13)
|
||||
{
|
||||
handleResult(this, true);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).keyup(function(e)
|
||||
{
|
||||
if (e.keyCode == 27)
|
||||
{
|
||||
handleResult($('#chatInput')[0].getTextBox(), false);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).keydown(function(e)
|
||||
{
|
||||
if (e.keyCode == 9)
|
||||
{
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
else if (e.keyCode == 33)
|
||||
{
|
||||
let buf = $('#chatBuffer');
|
||||
buf.scrollTop(buf.scrollTop() - 50);
|
||||
}
|
||||
else if (e.keyCode == 34)
|
||||
{
|
||||
let buf = $('#chatBuffer');
|
||||
buf.scrollTop(buf.scrollTop() + 50);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('message', function(event)
|
||||
{
|
||||
var item = event.data;
|
||||
|
||||
if (item.meta && item.meta == 'openChatBox')
|
||||
{
|
||||
inputShown = true;
|
||||
|
||||
$('#chat').stop().css('opacity', '1');
|
||||
|
||||
$('#chatInputHas').show();
|
||||
$('#chatInput')[0].doFocus();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use some templating stuff for this
|
||||
var colorR = parseInt(item.color[0]);
|
||||
var colorG = parseInt(item.color[1]);
|
||||
var colorB = parseInt(item.color[2]);
|
||||
|
||||
var name = item.name.replace('<', '<');
|
||||
var message = item.message.replace('<', '<');
|
||||
|
||||
name = colorize(name);
|
||||
message = colorize(message);
|
||||
|
||||
var buf = $('#chatBuffer');
|
||||
|
||||
var nameStr = '';
|
||||
|
||||
if (name != '')
|
||||
{
|
||||
nameStr = '<strong style="color: rgb(' + colorR + ', ' + colorG + ', ' + colorB + ')">' + name + ': </strong>';
|
||||
}
|
||||
|
||||
buf.find('ul').append('<li>' + nameStr + message + '</li>');
|
||||
buf.scrollTop(buf[0].scrollHeight - buf.height());
|
||||
|
||||
$('#chat').stop().css('opacity', '1');
|
||||
|
||||
startHideChat();
|
||||
}, false);
|
||||
});
|
4
resources/[system]/chat/html/config.js
Normal file
4
resources/[system]/chat/html/config.js
Normal file
@ -0,0 +1,4 @@
|
||||
window.CONFIG = {
|
||||
defaultTemplate: '<b>{0}</b>: {1}',
|
||||
fadeTimeout: 7000,
|
||||
};
|
105
resources/[system]/chat/html/index.css
Normal file
105
resources/[system]/chat/html/index.css
Normal file
@ -0,0 +1,105 @@
|
||||
* {
|
||||
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;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chat-window {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
width: 38%;
|
||||
height: 240px;
|
||||
|
||||
background-color: rgba(52, 73, 94, 0.7);
|
||||
-webkit-animation-duration: 2s;
|
||||
}
|
||||
|
||||
|
||||
.chat-messages {
|
||||
position: relative;
|
||||
height: 95%;
|
||||
font-size: 1.2rem;
|
||||
margin: 5px;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
|
||||
.chat-input {
|
||||
font-size: 1.2rem;
|
||||
position: absolute;
|
||||
|
||||
top: 257px;
|
||||
left: 15px;
|
||||
width: 38%;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.prefix {
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-size: 1.1rem;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
padding-left: 27px;
|
||||
color: white;
|
||||
background-color: rgba(44, 62, 80, 1.0);
|
||||
width: 100%;
|
||||
border-width: 0;
|
||||
height: 34px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msg {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.multiline {
|
||||
margin-left: 1.5rem;
|
||||
text-indent: -1.5rem;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
list-style-type: none;
|
||||
padding: 5px;
|
||||
padding-left: 27px;
|
||||
font-size: 1.1rem;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
background-color: rgba(44, 62, 80, 1.0);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.help {
|
||||
color: #b0bbbd;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #b0bbbd;
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
margin-bottom: 5px;
|
||||
}
|
96
resources/[system]/chat/html/index.html
Normal file
96
resources/[system]/chat/html/index.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- App Template -->
|
||||
<script type="text/x-template" id="app_template">
|
||||
<div id="app">
|
||||
<div class="chat-window" :class="{ 'fadeOut animated': !showWindow }">
|
||||
<div class="chat-messages" ref="messages">
|
||||
<message v-for="msg in messages"
|
||||
:multiline="msg.multiline"
|
||||
:args="msg.args"
|
||||
:template="msg.template"
|
||||
:key="msg">
|
||||
</message>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-input" v-show="showInput">
|
||||
<span class="prefix">➤</span>
|
||||
<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">
|
||||
</textarea>
|
||||
<suggestions :message="message" :suggestions="suggestions">
|
||||
</suggestions>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Message Template -->
|
||||
<script type="text/x-template" id="message_template">
|
||||
<div class="msg" :class="{ multiline }">
|
||||
<span v-html="textEscaped"></span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Suggestions Template -->
|
||||
<script type="text/x-template" id="suggestions_template">
|
||||
<ul class="suggestions" v-show="currentSuggestions.length > 0">
|
||||
<li class="suggestion" v-for="s in currentSuggestions">
|
||||
<p>
|
||||
<span :class="{ 'disabled': s.disabled }">
|
||||
{{s.name}}
|
||||
</span>
|
||||
<span class="param"
|
||||
v-for="(p, index) in s.params"
|
||||
:class="{ 'disabled': p.disabled }">
|
||||
[{{p.name}}]
|
||||
</span>
|
||||
</p>
|
||||
<small class="help">
|
||||
<template v-if="!s.disabled">
|
||||
{{s.help}}
|
||||
</template>
|
||||
<template v-for="p in s.params" v-if="!p.disabled">
|
||||
{{p.help}}
|
||||
</template>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" src="./Suggestions.js"></script>
|
||||
<script type="text/javascript" src="./Message.js"></script>
|
||||
<script type="text/javascript" src="./App.js"></script>
|
||||
|
||||
<!-- Main Entry -->
|
||||
<script type="text/javascript">
|
||||
const instance = new Vue({
|
||||
el: '#app',
|
||||
render: h => h(window.APP),
|
||||
});
|
||||
window.instance = instance;
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,103 +0,0 @@
|
||||
(function ($) {
|
||||
$.fn.fakeTextbox = function () {
|
||||
|
||||
return this.each(function () {
|
||||
|
||||
var $me = $(this),
|
||||
cursorTimer,
|
||||
$tb = $('<input type="text" class="fake" />');
|
||||
|
||||
if ($me.data('ftbftw')) {
|
||||
console.log('already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
$me.data('ftbftw', 1);
|
||||
|
||||
$tb.insertAfter($me);
|
||||
|
||||
function appendCaret(toHere, position, selStart, selEnd) {
|
||||
if (position === selStart) {
|
||||
toHere += "</div><div class='caret'>";
|
||||
}
|
||||
if (position === selEnd) {
|
||||
toHere += "</div><div>";
|
||||
}
|
||||
return toHere;
|
||||
}
|
||||
|
||||
function syncTextbox() {
|
||||
var tbVal = $tb.val().replace('<', '<');
|
||||
var tbLen = tbVal.length;
|
||||
var selStart = $tb.get(0).selectionStart;
|
||||
var selEnd = $tb.get(0).selectionEnd;
|
||||
var newOut = '<div>';
|
||||
|
||||
for (var i = 0; i < tbLen; i++) {
|
||||
newOut = appendCaret(newOut, i, selStart, selEnd);
|
||||
newOut += tbVal[i];
|
||||
}
|
||||
|
||||
$me.html(colorize(appendCaret(newOut, i, selStart, selEnd) + '</div>'));
|
||||
if (selStart != selEnd) {
|
||||
$('.caret', $me).addClass('selection');
|
||||
}
|
||||
}
|
||||
|
||||
$me.click(function () {
|
||||
$tb.focus();
|
||||
});
|
||||
|
||||
$tb.bind("change keypress keyup", function()
|
||||
{
|
||||
setTimeout(syncTextbox, 1); //
|
||||
})
|
||||
.blur(function () {
|
||||
clearInterval(cursorTimer);
|
||||
cursorTimer = null;
|
||||
var $cursor = $('.caret', $me);
|
||||
$cursor.css({
|
||||
visibility: 'visible'
|
||||
});
|
||||
$me.removeClass('focused');
|
||||
}).focus(function () {
|
||||
if (!cursorTimer) {
|
||||
$me.addClass('focused');
|
||||
cursorTimer = window.setInterval(function () {
|
||||
var $cursor = $('.caret', $me);
|
||||
if ($cursor.hasClass('selection') || $cursor.css('visibility') === 'hidden') {
|
||||
$cursor.css({
|
||||
visibility: 'visible'
|
||||
});
|
||||
} else {
|
||||
$cursor.css({
|
||||
visibility: 'hidden'
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
this.doFocus = function()
|
||||
{
|
||||
$tb.focus();
|
||||
};
|
||||
|
||||
this.onPress = function(f)
|
||||
{
|
||||
$tb.bind('keypress', f);
|
||||
};
|
||||
|
||||
this.getTextBox = function()
|
||||
{
|
||||
return $tb;
|
||||
};
|
||||
|
||||
syncTextbox();
|
||||
|
||||
if ($me.hasClass('initFocus')) {
|
||||
$tb.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
28
resources/[system]/chat/sv_chat.lua
Normal file
28
resources/[system]/chat/sv_chat.lua
Normal file
@ -0,0 +1,28 @@
|
||||
RegisterServerEvent('chatCommandEntered')
|
||||
RegisterServerEvent('chatMessageEntered')
|
||||
RegisterServerEvent('initialSuggestions')
|
||||
|
||||
|
||||
AddEventHandler('chatMessageEntered', function(author, message)
|
||||
if not message or not author then
|
||||
return
|
||||
end
|
||||
|
||||
TriggerEvent('chatMessage', source, author, message)
|
||||
|
||||
if not WasEventCanceled() then
|
||||
print("No cancel")
|
||||
TriggerClientEvent('chatMessage', -1, author, { 0, 0, 0 }, message)
|
||||
end
|
||||
|
||||
print(author .. ': ' .. 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)
|
Loading…
x
Reference in New Issue
Block a user