87 |
${args[0]}${name.replaceAll(args[0], "")}${params}
88 |
${curHelp}
89 |
`;
90 | }
91 | }
92 | chatSuggestions.style.display = chatSuggestions.innerHTML == "" ? "none" : "block";
93 | }
94 |
95 | $("#chat-input").on("input", GenerateSuggestions);
96 |
97 | document.addEventListener('DOMContentLoaded', () => {
98 | const chatForm = document.getElementById('chat-form');
99 | const chatInput = document.getElementById('chat-input');
100 | const settingsButton = document.querySelector('.settings-btn');
101 |
102 | settingsButton.addEventListener('mousedown', startDrag);
103 |
104 | chatForm.addEventListener('submit', (event) => {
105 | event.preventDefault();
106 | const messageText = chatInput.value.trim();
107 | if (messageText !== '') {
108 | $.post(`https://${GetParentResourceName()}/sendMessage`, JSON.stringify({
109 | message: messageText,
110 | }));
111 | if (messageText.startsWith("/")) {
112 | closeChat()
113 | }
114 | }
115 | });
116 |
117 | chatInput.addEventListener('focus', () => {
118 | clearTimeout(chatInputTimeout);
119 | });
120 | });
121 |
122 | let lastMsg = [];
123 | let curLastMsg = 0;
124 | let suggestions = {};
125 |
126 | function openChat() {
127 | const chatBody = document.body;
128 | if (chatBody.style.display !== 'none' && chatBody.classList.contains('visible')) {
129 | return;
130 | }
131 |
132 | document.getElementById('chat-input').value = '';
133 | chatBody.style.display = 'block';
134 | chatBody.style.animation = 'fadeIn 0.5s ease';
135 | // resetChatInputTimeout();
136 | chatClosedByUser = false;
137 |
138 | const chatInputEl = document.getElementById('chat-input');
139 | chatInputEl.focus();
140 | }
141 |
142 |
143 | function closeChat() {
144 | const suggestionContainer = document.getElementById('suggestion-container');
145 | document.body.addEventListener('animationend', function (event) {
146 | if (event.animationName === 'fadeOut') {
147 | document.body.style.display = 'none';
148 | document.body.classList.remove('visible');
149 | chatClosedByUser = true;
150 | suggestionContainer.classList.remove('visible');
151 |
152 | suggestionContainer.style.display = 'none';
153 | }
154 | }, { once: true });
155 | document.body.style.animation = 'fadeOut 0.5s ease forwards';
156 | $.post(`https://${GetParentResourceName()}/closeChat`, JSON.stringify({}));
157 | }
158 |
159 |
160 | window.addEventListener('keydown', function (event) {
161 | if (event.key === 'Escape') {
162 | closeChat();
163 | }
164 | });
165 |
166 | function resetChatInputTimeout() {
167 | clearTimeout(chatInputTimeout);
168 | chatInputTimeout = setTimeout(() => {
169 | if (!chatClosedByUser) {
170 | closeChat()
171 | }
172 | }, 5000);
173 | }
174 |
175 | function createMessageElement(user, text, playerId, job, bgcolor, icon) {
176 | const messageDiv = document.createElement('div');
177 | messageDiv.classList.add('message');
178 |
179 | if (bgcolor) {
180 | messageDiv.style.background = bgcolor;
181 | }
182 |
183 |
184 | const typeIcon = document.createElement('div');
185 | typeIcon.classList.add('icon');
186 |
187 | const iconElement = document.createElement('i');
188 | iconElement.classList.add('fa', `fa-${icon}`);
189 | typeIcon.appendChild(iconElement);
190 |
191 | const userBoxDiv = document.createElement('div');
192 | userBoxDiv.classList.add('user-box');
193 | userBoxDiv.textContent = user;
194 |
195 | const messageContentDiv = document.createElement('div');
196 | messageContentDiv.classList.add('message-content');
197 | const textSpan = document.createElement('span');
198 | textSpan.classList.add('text');
199 | textSpan.textContent = text;
200 |
201 | const timestamp = document.createElement('span');
202 | timestamp.classList.add('timestamp');
203 | const now = new Date();
204 | timestamp.textContent = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
205 |
206 | const metaDiv = document.createElement('div');
207 | metaDiv.classList.add('meta-info');
208 |
209 | if (playerId) {
210 | const playerIdSpan = document.createElement('span');
211 | playerIdSpan.classList.add('player-id');
212 | playerIdSpan.textContent = 'ID ' + playerId;
213 | metaDiv.appendChild(playerIdSpan);
214 | }
215 |
216 | if (job) {
217 | const jobDiv = document.createElement('div');
218 | jobDiv.classList.add('user-job');
219 | jobDiv.textContent = job;
220 | metaDiv.appendChild(jobDiv);
221 | }
222 |
223 | messageDiv.appendChild(typeIcon);
224 | metaDiv.appendChild(timestamp);
225 | messageContentDiv.appendChild(textSpan);
226 | messageDiv.appendChild(userBoxDiv);
227 | messageDiv.appendChild(messageContentDiv);
228 | messageDiv.appendChild(metaDiv);
229 | messageDiv.style.opacity = '3';
230 |
231 | setTimeout(() => {
232 | messageDiv.style.transition = 'opacity 0.9s ease';
233 | messageDiv.style.opacity = '1';
234 | }, 200);
235 |
236 | openChat();
237 | return messageDiv;
238 | }
239 |
240 |
241 | window.addEventListener('message', (event) => {
242 | let data = event.data;
243 | if (data.action == 'clearChat') {
244 | clearChat();
245 | }
246 | });
247 |
248 | function clearChat() {
249 | const chatMessages = document.getElementById('chat-messages');
250 | chatMessages.innerHTML = '';
251 | }
252 |
253 | const chatInputEl = document.getElementById('chat-input');
254 | const suggestionContainerEl = document.getElementById('suggestion-container');
255 |
256 | chatInputEl.addEventListener('input', () => {
257 | const inputValue = chatInputEl.value.trim().toLowerCase();
258 | if (inputValue) {
259 | suggestionContainerEl.classList.add('visible');
260 | } else {
261 | suggestionContainerEl
262 | suggestionContainerEl.classList.remove('visible');
263 | }
264 | });
265 |
266 | suggestionContainerEl.addEventListener('click', (event) => {
267 | if (event.target.classList.contains('suggestion_command')) {
268 | const suggestionText = event.target.textContent.trim();
269 | chatInputEl.value = suggestionText;
270 | suggestionContainerEl.classList.remove('visible');
271 | chatInputEl.focus();
272 | }
273 | });
274 |
275 |
276 | function startDrag(event) {
277 | isChatDragging = true;
278 | offset = {
279 | x: event.clientX,
280 | y: event.clientY
281 | };
282 |
283 | document.addEventListener('mousemove', dragChat);
284 | document.addEventListener('mouseup', stopDrag);
285 | }
286 |
287 | function dragChat(event) {
288 | if (isChatDragging) {
289 | const chatContainer = document.querySelector('.chat');
290 | const chatContainerSize = document.querySelector('.chat-container').getBoundingClientRect();
291 |
292 | /* This is extremely janky, but I can't be arsed to do in another way */
293 | const newChatLeft = Math.max(-30, Math.min(window.innerWidth - chatContainerSize.width * 1.05, event.clientX - offset.x + chatContainer.offsetLeft)) + 'px';
294 | const newChatTop = Math.min(chatContainerSize.height * 0.925, Math.max(-(chatContainerSize.height * 1.05), event.clientY - offset.y + chatContainer.offsetTop)) + 'px';
295 |
296 | chatContainer.style.left = newChatLeft;
297 | chatContainer.style.top = newChatTop;
298 |
299 | offset = {
300 | x: event.clientX,
301 | y: event.clientY
302 | };
303 | }
304 | }
305 |
306 | function stopDrag() {
307 | if (isChatDragging) {
308 | document.removeEventListener('mousemove', dragChat);
309 | document.removeEventListener('mouseup', stopDrag);
310 | isChatDragging = false;
311 | }
312 | }
313 |
314 | let chatMessages = document.getElementById('chat-messages');
315 | let messageIndex = -1;
316 | let messageHistory = [];
317 |
318 | document.addEventListener('keydown', (event) => {
319 | if (event.key === 'ArrowUp') {
320 | if (messageIndex === -1) {
321 | messageIndex = messageHistory.length - 1;
322 | chatInputEl.value = messageHistory[messageIndex];
323 | } else if (messageIndex > 0) {
324 | messageIndex--;
325 | chatInputEl.value = messageHistory[messageIndex];
326 | }
327 | } else if (event.key === 'ArrowDown') {
328 | if (messageIndex < messageHistory.length - 1) {
329 | messageIndex++;
330 | chatInputEl.value = messageHistory[messageIndex];
331 | } else {
332 | chatInputEl.value = '';
333 | messageIndex = -1;
334 | }
335 | }
336 | });
337 |
338 |
339 | function appendMessageToHistory(message) {
340 | messageHistory.push(message);
341 | if (messageHistory.length > 10) {
342 | messageHistory.shift();
343 | }
344 | }
345 |
346 | const chatForm = document.getElementById('chat-form');
347 | chatForm.addEventListener('submit', (event) => {
348 | event.preventDefault();
349 | const messageText = chatInputEl.value.trim();
350 | if (messageText !== '') {
351 | appendMessageToHistory(messageText);
352 | }
353 | });
354 |
355 |
356 | // const chatMessages = document.getElementById('chat-messages');
357 | // const messageElement = createMessageElement('ENT510', 'text', 1, 'ambulance', 'black', 'globe');
358 |
359 | // chatMessages.appendChild(messageElement);
360 |
--------------------------------------------------------------------------------
/ui/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
2 |
3 | :root {
4 | --primary-color: #146049;
5 | --background-color: #2c3e50;
6 | --header-color: #1abc9c;
7 | --light-bg-color: #232729e1;
8 | --text-color: #f3efef;
9 | --border-color: #ccc;
10 | --input-bg-color: #fff;
11 | --button-color: #38D9A9;
12 | --button-hover-color: #1abc9c;
13 | }
14 |
15 | body {
16 | font-family: 'roboto', monospace;
17 | display: none;
18 | justify-content: center;
19 | align-items: center;
20 | margin: 0;
21 | user-select: none;
22 | animation: fadeOut 0.5s ease forwards;
23 | overflow: hidden;
24 | }
25 |
26 | .chat-container {
27 | width: 95%;
28 | max-width: 450px;
29 | min-height: 330px;
30 | max-height: 330px;
31 | border-radius: 8px;
32 | position: absolute;
33 | top: 50%;
34 | left: 30px;
35 | transform: translateY(-50%);
36 | display: flex;
37 | flex-direction: column;
38 | }
39 |
40 | .chat {
41 | position: absolute;
42 | width: 100%;
43 | height: 100%;
44 | }
45 |
46 | .chat-messages {
47 | padding: 3px;
48 | overflow-y: auto;
49 | right: 60px;
50 | scrollbar-width: none;
51 | margin-top: 20px;
52 | margin-left: 20px;
53 | overflow-y: auto;
54 | }
55 |
56 | .chat-messages::-webkit-scrollbar {
57 | display: none;
58 | }
59 |
60 | .message {
61 | position: relative;
62 | background-color: #312B2B;
63 | border-radius: 10px;
64 | padding: 8px 15px;
65 | margin-bottom: 15px;
66 | min-width: 80%;
67 | max-width: 80%;
68 | opacity: 0;
69 | animation: fadeIn 0.5s ease forwards;
70 | font-size: 18px;
71 | overflow: hidden;
72 | }
73 |
74 |
75 | .message .user {
76 | font-weight: bold;
77 | color: var(--primary-color);
78 | margin-bottom: 5px;
79 | margin-left: 20px;
80 | }
81 |
82 | .message .text {
83 | margin-top: 8px;
84 | color: white;
85 | word-break: break-word;
86 | font-size: 15px;
87 | font-family: 'Copperplate', sans-serif;
88 | max-height: 100px;
89 | overflow-y: auto;
90 | }
91 |
92 |
93 | .message .user-box {
94 | background-color: var(--primary-color);
95 | color: #fff;
96 | font-size: 11px;
97 | padding: 3px;
98 | border-radius: 3px;
99 | margin-bottom: 8px;
100 | max-width: fit-content;
101 | margin-left: 27px;
102 | }
103 |
104 | .message .icon {
105 | background-color: var(--primary-color);
106 | color: #fff;
107 | font-size: 11px;
108 | padding: 3px;
109 | border-radius: 3px;
110 | margin-bottom: 8px;
111 | max-width: fit-content;
112 | margin-left: 0px;
113 | position: absolute;
114 | }
115 |
116 | .message .meta-info {
117 | display: flex;
118 | align-items: center;
119 | gap: 10px;
120 | font-size: 11px;
121 | color: white;
122 | position: absolute;
123 | top: 8px;
124 | right: 0;
125 | padding-right: 10px;
126 | }
127 |
128 | .message .player-id,
129 | .message .user-job,
130 | .message .timestamp {
131 | background-color: var(--primary-color);
132 | padding: 3px 6px;
133 | border-radius: 3px;
134 | font-size: 11px;
135 | color: white;
136 | white-space: nowrap;
137 | max-width: fit-content;
138 | width: auto;
139 | margin-bottom: 8px;
140 | font-family: 'Roboto', sans-serif;
141 | }
142 |
143 |
144 |
145 | .suggestion-container {
146 | font-family: 'Roboto', sans-serif;
147 | position: absolute;
148 | top: 90%;
149 | width: 28vh;
150 | min-height: 24px;
151 | max-height: 210px;
152 | max-width: 100%;
153 | overflow-y: auto;
154 | /* background-color: rgba(49, 43, 43, 0.539); */
155 | padding: 5px;
156 | border-radius: 2px;
157 | display: none;
158 | }
159 |
160 | .suggestion-container::-webkit-scrollbar {
161 | width: 8px;
162 | }
163 |
164 | .suggestion-container::-webkit-scrollbar-track {
165 | background: transparent;
166 | }
167 |
168 |
169 |
170 | .suggestion-container.visible {
171 | display: block;
172 | }
173 |
174 | .suggestion {
175 | background-color: var(--primary-color);
176 | color: #fff;
177 | padding: 5px 10px;
178 | border-radius: 5px;
179 | max-width: 100%;
180 | margin-bottom: 7px;
181 | cursor: pointer;
182 | background-color: rgba(49, 43, 43, 0.539);
183 | }
184 |
185 | .suggestion:hover {
186 | background-color: var(--button-hover-color);
187 | }
188 |
189 |
190 | .chat-input {
191 | position: absolute;
192 | bottom: 310px;
193 | left: 30px;
194 | display: flex;
195 | padding: 15px;
196 | max-width: 100%;
197 | }
198 |
199 | .chat-input input {
200 | flex: 1;
201 | padding: 12px;
202 | border-radius: 8px;
203 | margin-right: 15px;
204 | background-color: rgb(43, 39, 39);
205 | box-shadow: none;
206 | box-sizing: border-box;
207 | width: 28vh;
208 | min-height: 44px;
209 | max-height: 44px;
210 | border: none;
211 | color: white;
212 | }
213 |
214 | .chat-input input:focus {
215 | outline: none;
216 | }
217 |
218 | .send-btn {
219 | background-color: var(--button-color);
220 | color: #fff;
221 | border: none;
222 | padding: 10px 15px;
223 | border-radius: 5px;
224 | cursor: pointer;
225 | display: flex;
226 | justify-content: center;
227 | align-items: center;
228 | transition: background-color 0.3s;
229 | min-height: 40px;
230 | max-height: 40px;
231 | }
232 |
233 | .send-btn:hover {
234 | background-color: var(--button-hover-color);
235 | }
236 |
237 | .settings-btn {
238 | background-color: var(--button-color);
239 | color: #fff;
240 | border: none;
241 | padding: 10px 15px;
242 | border-radius: 5px;
243 | cursor: pointer;
244 | display: flex;
245 | justify-content: center;
246 | align-items: center;
247 | transition: background-color 0.3s;
248 | margin-left: 15px;
249 | min-height: 40px;
250 | max-height: 40px;
251 | }
252 |
253 | .settings-btn:hover {
254 | background-color: var(--button-hover-color);
255 | }
256 |
257 |
258 | @keyframes fadeIn {
259 | from {
260 | opacity: 0;
261 | }
262 |
263 | to {
264 | opacity: 4;
265 | }
266 | }
267 |
268 | @keyframes fadeOut {
269 | from {
270 | opacity: 1;
271 | }
272 |
273 | to {
274 | opacity: 0;
275 | }
276 | }
--------------------------------------------------------------------------------
/utils/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "noPermissionCommand" : "You Dont Have Permission For This Command",
3 | "adAlreadyActive" : "An ad is already active. Please wait until it finishes.",
4 | "adNeedMessage" : "You need to provide a message for the ad",
5 | "adFinished" : "Your ad has finished." ,
6 | "adScheduled" : "Your ad has been scheduled for the next 30 minutes",
7 | "isInCooldown" : "You can't send messages that quickly",
8 | "BlacklistedWords" : "You used Blacklisted words! You have now been reported!"
9 | }
--------------------------------------------------------------------------------
/utils/utils.lua:
--------------------------------------------------------------------------------
1 | SharedCore = {}
2 |
3 | function SharedCore:GetDebug(...)
4 | local enableDebug = GetConvar("LGF_Chat:Debugging", "false")
5 | local args = { ... }
6 | if enableDebug == "true" then
7 | for i, arg in ipairs(args) do
8 | if type(arg) == 'table' then
9 | args[i] = json.encode(arg, { sort_keys = true, indent = true })
10 | else
11 | args[i] = '^0' .. tostring(arg)
12 | end
13 | end
14 |
15 | local formattedMessage = '^5[DEBUG] ^7' .. table.concat(args, '\t')
16 | print(formattedMessage)
17 | end
18 | end
19 |
20 | function SharedCore:GetKeyTraduction(key)
21 | local Language = GetConvar("LGF_Chat:Translate", "en")
22 | local File = LoadResourceFile(cache.resource, ("utils/locales/%s.json"):format(Language))
23 |
24 | if not File then
25 | warn("Defined Language does not exist in locales/*.json please inform server owner.")
26 | File = LoadResourceFile(cache.resource, "utils/en.json")
27 | end
28 |
29 | local locales = json.decode(File)
30 | return locales[key] or ("[MISSING TRANSLATION: %s]"):format(key)
31 | end
32 |
33 | return SharedCore
34 |
--------------------------------------------------------------------------------