chat overhaul

pull/90/head
Aevann 2023-01-21 12:36:21 +02:00
parent 111f664440
commit f2fdefa8cd
14 changed files with 535 additions and 34927 deletions

View File

@ -17,6 +17,7 @@ COPY requirements.txt /etc/requirements.txt
RUN pip3 install -r /etc/requirements.txt
RUN mkdir /images
RUN mkdir /chat_images
RUN mkdir /songs
RUN mkdir /videos
RUN mkdir /audio

View File

@ -0,0 +1,123 @@
#chat-window {
max-height: calc(100vh - 220px);
overflow-y: auto;
background-color: transparent !important;
}
#online {
max-height: calc(100vh - 200px);
overflow-y: auto;
background-color: var(--background) !important;
}
#chat-window .chat-profile {
min-width: 42px;
width: 42px;
height: 42px;
}
#chat-window::-webkit-scrollbar {
display: none;
}
#chat-window {
-ms-overflow-style: none;
scrollbar-width: none;
}
.chat-mention {
background-color: #{{v.themecolor}}55;
border-radius: 3px;
}
p, h1, h2, h3, h4, h5 {
display: inherit;
}
blockquote + :not(blockquote) {
display: inline-block;
margin-top: 0.5rem;
}
blockquote {
margin-top: 0.5rem;
}
.chat-group {
margin-top: 1rem;
}
@media (max-width: 768px) {
#shrink * {
font-size: 14px !important;
}
.chat-group {
margin-top: 0.5rem;
}
#chat-window {
max-height: 62vh;
}
}
p {
margin: 0;
}
.chat-line .btn {
background-color: transparent !important;
}
.chat-line-content {
width: 100%;
}
.cdiv {
overflow: hidden;
margin-left: 27px;
}
.quote, .del {
padding: 0 0.5rem !important;
border: none !important;
float: right;
color: var(--black);
font-size: 10px;
}
lite-youtube {
min-width: min(80vw,500px);
}
.chat-group:nth-child(even) {
background: rgba(255, 255, 255, 0.05);
}
.btn-secondary {
border: none !important;
}
#cancel {
font-size: 10px;
padding: 0 0 0.2rem 0.5rem;
}
.btn:focus, .btn.focus {
box-shadow: none;
}
.time {
font-size: 10px;
opacity: 0.95;
}
.chat-line:target {
background: #ffffff44 !important;
}
.QuotedMessage * {
font-size: 10px !important;
}
.quotes {
margin-top: 0.5rem;
}

View File

@ -1,329 +0,0 @@
/* src/App.css */
html,
body {
overscroll-behavior-y: none;
}
html {
height: -webkit-fill-available;
}
body {
min-height: 100vh;
min-height: calc(var(--vh, 1vh) * 100);
overflow: hidden;
min-height: -webkit-fill-available;
}
.App {
position: fixed;
width: 100vw;
display: flex;
overflow: hidden;
}
.App-wrapper {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 2rem;
}
.App-heading {
flex-basis: 3rem;
display: flex;
align-items: center;
}
.App-heading small {
opacity: 0.2;
font-size: 10px;
}
.App-side {
height: 100%;
flex: 1;
background: var(--gray-500);
position: relative;
}
.App-content {
position: relative;
flex: 3;
height: 62vh;
height: calc(var(--vh, 1vh) * 68);
max-height: 1000px;
overflow: auto;
-ms-overflow-style: none;
scrollbar-width: none;
display: flex;
flex-direction: column;
}
.App-content::-webkit-scrollbar {
display: none;
}
.App-drawer {
z-index: 2;
display: flex;
background: rgb(var(--background));
height: 100%;
}
.App-center {
display: flex;
align-items: flex-start;
}
.App-bottom-wrapper {
display: flex;
align-items: flex-start;
}
.App-bottom {
flex: 3;
}
.App-bottom-dummy {
flex: 1;
}
.App-bottom-extra {
padding: .25rem;
}
@media screen and (max-width: 1100px) {
.App-wrapper {
margin: 0 auto;
}
.App-heading {
padding: 0 1rem;
}
.App-side {
display: none;
}
.App-bottom-dummy {
display: none;
}
.App-bottom-wrapper {
padding-right: 1rem;
padding-left: 1rem;
}
}
lite-youtube {
min-width: min(80vw, 500px);
}
.btn-secondary {
border: none !important;
}
.btn-secondary:focus {
border: none !important;
box-shadow: none !important;
}
/* src/features/chat/UserList.css */
.UserList {
padding: 1rem;
flex: 1;
}
.UserList-heading {
display: flex;
align-items: center;
justify-content: space-between;
}
.UserList-heading h5 {
margin-right: 2rem;
}
.UserList ul {
max-height: calc(var(--vh, 1vh) * 50);
overflow: auto;
}
.UserList ul::-webkit-scrollbar {
display: none;
}
/* src/features/chat/ChatHeading.css */
.ChatHeading {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.ChatHeading i {
margin-right: 0.5rem;
}
/* src/features/chat/Username.css */
.Username {
display: inline-flex;
align-items: center;
}
.Username > a {
font-weight: bold;
margin-left: 8px;
}
/* src/features/chat/QuotedMessageLink.css */
.QuotedMessageLink {
font-size: 10px;
}
/* src/features/chat/ChatMessage.css */
.ChatMessage {
position: relative;
padding-right: 1.5rem;
max-height: 300px;
overflow-x: scroll;
}
.ChatMessage__isDm {
background: var(--gray-800);
border-top: 1px dashed var(--primary);
border-bottom: 1px dashed var(--primary);
}
.ChatMessage__isOptimistic {
opacity: 0.5;
}
.ChatMessage p {
margin: 0;
}
.ChatMessage .btn {
border: none !important;
}
.ChatMessage-top {
display: flex;
align-items: center;
}
.ChatMessage-timestamp {
margin-left: 0.5rem;
opacity: 0.5;
font-size: 10px;
}
.ChatMessage-bottom {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 30px;
overflow: hidden;
}
.ChatMessage-content {
margin-right: 0.5rem;
word-wrap: break-word;
display: inline-block;
}
.ChatMessage-button {
margin: 0 0.5rem;
}
.ChatMessage-button i {
margin-right: 0.5rem;
}
.ChatMessage-button__confirmed {
color: red !important;
}
.ChatMessage-quoted-link {
padding-left: 2rem;
}
.ChatMessage-actions-button {
position: absolute;
top: 0;
right: 0;
cursor: pointer;
z-index: 5;
background: none !important;
border: none !important;
box-shadow: none !important;
display: flex;
align-items: center;
}
.ChatMessage-actions-button button {
background: none !important;
border: none !important;
padding: 0 !important;
}
.ChatMessage-actions-button button i {
position: relative;
top: 3px;
margin-right: 1rem;
}
.ChatMessage-actions {
z-index: 1;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(20, 20, 20, 0.85);
display: flex;
align-items: center;
justify-content: flex-end;
padding: 1rem;
padding-right: 3rem;
animation: fading-in 0.3s ease-in-out forwards;
}
.ChatMessage-actions button {
font-size: 10px;
background: none !important;
}
.ChatMessageList {
flex: 1;
}
.ChatMessageList-group {
margin-bottom: 1rem;
padding: 0.3rem;
border-radius: 8px;
}
.ChatMessageList-group:nth-child(even) {
background: rgba(255, 255, 255, 0.025);
}
/* src/drawers/BaseDrawer.css */
.BaseDrawer {
flex: 1;
padding-right: 2rem;
overflow: hidden;
}
/* src/features/emoji/EmojiGenres.css */
/* src/features/emoji/EmojiDrawer.css */
.EmojiDrawer-options {
display: flex;
align-items: center;
justify-content: space-between;
}
/* src/features/chat/UserInput.css */
.UserInput {
position: relative;
display: flex;
align-items: center;
}
.UserInput-input {
flex: 1;
margin-right: 2rem;
min-height: 50px;
height: 50px;
max-height: 50px;
}
@media (min-width: 768px) {
.UserInput-input {
min-height: 70px !important;
height: 70px !important;
max-height: 70px !important;
}
}
.UserInput-emoji {
cursor: pointer;
font-size: 20px;
transform: rotateY(180deg);
margin-right: 1rem;
}
/* src/features/chat/UsersTyping.css */
.UsersTyping {
height: 18px;
display: inline-block;
font-size: 10px;
}
/* src/features/chat/QuotedMessage.css */
.QuotedMessage {
display: flex;
align-items: center;
justify-content: space-between;
}
.QuotedMessage-content {
margin-left: 1rem;
flex: 1;
max-width: 420px;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 1rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1 +1,299 @@
window.global = window
function timeSince(timeStamp) {
var now = new Date(),
secondsPast = (now.getTime() - timeStamp) / 1000;
if (secondsPast < 60) {
return parseInt(secondsPast) + 's';
}
if (secondsPast < 3600) {
return parseInt(secondsPast / 60) + 'm';
}
if (secondsPast <= 86400) {
return parseInt(secondsPast / 3600) + 'h';
}
if (secondsPast > 86400) {
day = timeStamp.getDate();
month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", "");
year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear();
return day + " " + month + year;
}
}
const ua=window.navigator.userAgent
let socket
socket=io()
const chatline = document.getElementsByClassName('chat-line')[0]
const box = document.getElementById('chat-window')
const textbox = document.getElementById('input-text')
const icon = document.getElementById('icon')
const vid = document.getElementById('vid').value
const vusername = document.getElementById('vusername').value
const site_name = document.getElementById('site_name').value
const slurreplacer = document.getElementById('slurreplacer').value
let notifs = 0;
let focused = true;
let is_typing = false;
let alert=true;
function flash(){
let title = document.getElementsByTagName('title')[0]
if (notifs >= 1 && !focused){
title.innerHTML = `[+${notifs}] Chat`;
if (alert) {
icon.href = escapeHTML(`/assets/images/${site_name}/alert.webp?v=3`)
alert=false;
}
else {
icon.href = escapeHTML(`/assets/images/${site_name}/icon.webp?v=3009`)
alert=true;
}
setTimeout(flash, 500)
}
else {
icon.href = escapeHTML(`/assets/images/${site_name}/icon.webp?v=3009`)
notifs = 0
title.innerHTML = 'Chat';
}
if (is_typing) {
is_typing = false
socket.emit('typing', false);
}
}
function handle_message(json) {
let text = json['text']
let text_html
if (slurreplacer == 'True') text_html = json['text_censored']
else text_html = json['text_html']
if (text_html.includes(`<a href="/id/${vid}">`)){
chatline.classList.add('chat-mention');
}
else {
chatline.classList.remove('chat-mention');
};
notifs = notifs + 1;
if (notifs == 1) {
setTimeout(flash, 500);
}
const users = document.getElementsByClassName('userlink');
const last_user = users[users.length-1].innerHTML;
const scrolled_down = (box.scrollHeight - box.scrollTop <= window.innerHeight)
if (last_user == json['username']) {
document.getElementsByClassName('userlink')[0].classList.add('d-none')
document.getElementsByClassName('avatar')[0].classList.add('d-none')
document.getElementsByClassName('time')[0].classList.add('d-none')
}
else {
document.getElementsByClassName('userlink')[0].classList.remove('d-none')
document.getElementsByClassName('avatar')[0].classList.remove('d-none')
document.getElementsByClassName('avatar-pic')[0].src = '/pp/' + json["user_id"]
if (json['hat'])
document.getElementsByClassName('avatar-hat')[0].src = json['hat'] + "?h=7"
else
document.getElementsByClassName('avatar-hat')[0].removeAttribute("src")
document.getElementsByClassName('userlink')[0].href = '/@' + json['username']
document.getElementsByClassName('userlink')[0].style.color = '#' + json['namecolor']
document.getElementsByClassName('time')[0].classList.remove('d-none')
document.getElementsByClassName('time')[0].innerHTML = timeSince(json['time']*1000) + ' ago'
}
document.getElementsByClassName('chat-line')[0].id = json['id']
document.getElementsByClassName('userlink')[0].innerHTML = json['username']
document.getElementsByClassName('text')[0].innerHTML = escapeHTML(text)
document.getElementsByClassName('chat-message')[0].innerHTML = text_html.replace(/data-src/g, 'src').replace(/data-cfsrc/g, 'src').replace(/style="display:none;visibility:hidden;"/g, '')
if (json['quotes']) {
document.getElementsByClassName('quotes')[0].classList.remove("d-none")
const quoted = document.getElementById(json['quotes'])
document.getElementsByClassName('QuotedMessageLink')[0].href = '#' + json['quotes']
document.getElementsByClassName('QuotedUser')[0].innerHTML = quoted.querySelector('.userlink').innerHTML
document.getElementsByClassName('QuotedMessage')[0].innerHTML = quoted.querySelector('.chat-message').innerHTML
}
else {
document.getElementsByClassName('quotes')[0].classList.add("d-none")
}
let line = document.getElementsByClassName('chat-line')[0].cloneNode(true)
register_new_elements(line);
bs_trigger(line)
if (last_user == json['username']) {
box.querySelector('.chat-group:last-child').append(line)
}
else {
const chatgroup = document.createElement("div");
chatgroup.className = "chat-group";
chatgroup.append(line)
box.append(chatgroup)
}
if (scrolled_down || json['username'] == vusername)
box.scrollTo(0, box.scrollHeight)
}
socket.on('speak', function(json) {
handle_message(json)
})
socket.on('catchup', function(json) {
for (const message of json) {
handle_message(message)
}
})
function send() {
const text = textbox.value.trim()
const files = document.getElementById('file').files
if (text || files)
{
let sending;
if (files[0]) sending = files[0]
else sending = ''
socket.emit('speak', {
"message": text,
"quotes": document.getElementById('quotes_id').value,
"file": sending,
});
textbox.value = ''
is_typing = false
socket.emit('typing', false);
autoExpand(textbox);
document.getElementById("quotes").classList.add("d-none")
document.getElementById("filename").innerHTML = '<i class="fas fa-image" style="font-size:1.3rem!important"></i>'
scroll_chat();
}
}
function quote(t) {
document.getElementById("quotes").classList.remove("d-none")
const text = t.parentElement.getElementsByClassName("text")[0].innerHTML.replace(/\*/g,"\\*").split('\n').pop()
document.getElementById('QuotedMessage').innerHTML = text
const username = t.parentElement.parentElement.getElementsByClassName('userlink')[0].innerHTML
document.getElementById('QuotedUser').innerHTML = username
const id = t.parentElement.parentElement.parentElement.parentElement.id
document.getElementById('quotes_id').value = id
document.getElementById('QuotedMessageLink').href = `#${id}`
}
textbox.addEventListener("keyup", function(e) {
if (e.key === 'Enter') {
e.preventDefault();
send();
}
})
socket.on('online', function(data){
document.getElementsByClassName('board-chat-count')[0].innerHTML = data.length
let online = ''
let online2 = '<b>Users Online</b>'
for (const u of data)
{
online += `<li><a href="/@${u}">@${u}</a></li>`
online2 += `<br>@${u}`
}
document.getElementById('online').innerHTML = online
document.getElementById('online2').setAttribute("data-bs-original-title", online2);
document.getElementById('online3').innerHTML = online
})
window.addEventListener('blur', function(){
focused=false
})
window.addEventListener('focus', function(){
focused=true
})
textbox.addEventListener("input", function() {
text = textbox.value
if (!text && is_typing==true){
is_typing=false;
socket.emit('typing', false);
}
else if (text && is_typing==false) {
is_typing=true;
socket.emit('typing', true);
}
})
socket.on('typing', function (users){
if (users.length==0){
document.getElementById('typing-indicator').innerHTML = '';
document.getElementById('loading-indicator').classList.add('d-none');
}
else if (users.length==1){
document.getElementById('typing-indicator').innerHTML = '<b>'+users[0]+"</b> is typing...";
document.getElementById('loading-indicator').classList.remove('d-none');
}
else if (users.length==2){
document.getElementById('typing-indicator').innerHTML = '<b>'+users[0]+"</b> and <b>"+users[1]+"</b> are typing...";
document.getElementById('loading-indicator').classList.remove('d-none');
}
else {
document.getElementById('typing-indicator').innerHTML = '<b>'+users[0]+"</b>, <b>"+users[1]+"</b>, and <b>"+users[2]+"</b> are typing...";
document.getElementById('loading-indicator').classList.remove('d-none');
}
})
function del(t) {
const chatline = t.parentElement.parentElement.parentElement.parentElement
socket.emit('delete', chatline.id);
chatline.remove()
}
socket.on('delete', function(text) {
const text_spans = document.getElementsByClassName('text')
for(const span of text_spans) {
if (span.innerHTML == text)
{
span.parentElement.parentElement.parentElement.parentElement.parentElement.remove()
}
}
})
function scroll_chat() {
setTimeout(function () {
box.scrollTo(0, box.scrollHeight)
}, 200);
setTimeout(function () {
box.scrollTo(0, box.scrollHeight)
}, 500);
setTimeout(function () {
box.scrollTo(0, box.scrollHeight)
}, 1000);
}
scroll_chat();
box.scrollTo(0, box.scrollHeight);
document.addEventListener('click', function (e) {
if (e.target.classList.contains('fa-trash-alt')) {
e.target.nextElementSibling.classList.remove('d-none');
e.target.classList.add('d-none');
}
else {
for (const btn of document.querySelectorAll('.delmsg:not(.d-none)')) {
btn.classList.add('d-none');
btn.previousElementSibling.classList.remove('d-none');
}
}
if (e.target.id == "cancel") {
document.getElementById("quotes").classList.add("d-none")
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -8,25 +8,17 @@ from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.config.const import *
from files.helpers.regex import *
from files.helpers.media import process_image
from files.helpers.sanitize import sanitize
from files.helpers.alerts import push_notif
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
if IS_LOCALHOST:
socketio = SocketIO(
app,
async_mode='gevent',
logger=True,
engineio_logger=True,
debug=True
)
else:
socketio = SocketIO(
app,
async_mode='gevent',
)
socketio = SocketIO(
app,
async_mode='gevent',
)
typing = {
f'{SITE_FULL}/chat': [],
@ -45,8 +37,6 @@ messages = cache.get(f'messages') or {
f'{SITE_FULL}/chat': [],
f'{SITE_FULL}/admin/chat': []
}
socket_ids_to_user_ids = {}
user_ids_to_socket_ids = {}
@app.get("/chat")
@limiter.limit(DEFAULT_RATELIMIT, key_func=get_ID)
@ -66,6 +56,13 @@ def admin_chat(v):
@limiter.limit("3/second;10/minute", key_func=get_ID)
@admin_level_required(PERMS['CHAT'])
def speak(data, v):
image = None
if data['file']:
name = f'/chat_images/{time.time()}'.replace('.','') + '.webp'
with open(name, 'wb') as f:
f.write(data['file'])
image = process_image(name, v)
limiter.check()
if v.is_banned: return '', 403
if TRUESCORE_CHAT_MINIMUM and v.truescore < TRUESCORE_CHAT_MINIMUM:
@ -79,32 +76,26 @@ def speak(data, v):
global messages
text = sanitize_raw_body(data['message'], False)[:CHAT_LENGTH_LIMIT]
if image: text += f'\n\n![]({image})'
if not text: return '', 400
text_html = sanitize(text, count_marseys=True)
quotes = data['quotes']
recipient = data['recipient']
data = {
"id": str(uuid.uuid4()),
"quotes": quotes,
"hat": v.hat_active(v)[0],
"user_id": v.id,
"dm": bool(recipient and recipient != ""),
"username": v.username,
"namecolor": v.name_color,
"text": text,
"text_html": text_html,
"base_text_censored": censor_slurs(text, 'chat'),
"text_censored": censor_slurs(text_html, 'chat'),
"time": int(time.time()),
}
if v.shadowbanned or not execute_blackjack(v, None, text, "chat"):
emit('speak', data)
elif recipient:
if user_ids_to_socket_ids.get(recipient):
recipient_sid = user_ids_to_socket_ids[recipient]
emit('speak', data, broadcast=False, to=recipient_sid)
else:
emit('speak', data, room=request.referrer, broadcast=True)
messages[request.referrer].append(data)
@ -148,10 +139,6 @@ def connect(v):
online[request.referrer].append(v.username)
refresh_online()
if not socket_ids_to_user_ids.get(request.sid):
socket_ids_to_user_ids[request.sid] = v.id
user_ids_to_socket_ids[v.id] = request.sid
emit('catchup', messages[request.referrer], room=request.referrer)
emit('typing', typing[request.referrer], room=request.referrer)
return '', 204
@ -167,10 +154,6 @@ def disconnect(v):
if v.username in typing[request.referrer]:
typing[request.referrer].remove(v.username)
if socket_ids_to_user_ids.get(request.sid):
del socket_ids_to_user_ids[request.sid]
del user_ids_to_socket_ids[v.id]
emit('typing', typing[request.referrer], room=request.referrer, broadcast=True)
leave_room(request.referrer)
@ -193,13 +176,13 @@ def typing_indicator(data, v):
@socketio.on('delete')
@limiter.limit(DEFAULT_RATELIMIT, key_func=get_ID)
@admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def delete(text, v):
for message in messages:
if message['text'] == text:
def delete(id, v):
for message in messages[request.referrer]:
if message['id'] == id:
messages[request.referrer].remove(message)
break
emit('delete', text, room=request.referrer, broadcast=True)
emit('delete', id, room=request.referrer, broadcast=True)
return '', 204

View File

@ -11,19 +11,90 @@
{% include "header.html" %}
{% include "modals/expanded_image.html" %}
{% include "modals/emoji.html" %}
<div
id="root"
data-id="{{v.id}}"
data-username="{{v.username}}"
data-admin="{{v.admin_level >= PERMS['POST_COMMENT_MODERATION_TOOLS_VISIBLE']}}"
data-censored="{{v.slurreplacer}}"
data-sitename="{{SITE_NAME}}"
data-themecolor="{{v.themecolor}}"
data-namecolor="{{v.namecolor}}"
data-hat="{{v.hat_active(v)[0]}}">
<div class="container pb-4">
<div class="row justify-content-around" id="main-content-row">
<div class="col h-100 {% block customPadding %}{% if request.path.startswith('/@') %}user-gutters{% else %}custom-gutters{% endif %}{% endblock %}" id="main-content-col">
<div class="border-right pb-1 pt-2 px-3">
<span id="online2" data-bs-html="true" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Users Online" class="text-muted">
<i class="far fa-user fa-sm mr-1"></i>
<span class="board-chat-count">0</span>
</span>
</div>
<div id="chat-line-template" class="d-none">
<div class="chat-line">
<div class="d-flex align-items-center">
<div class="pl-md-3 text-muted chat-line-content">
<div class="avatar profile-pic-20-wrapper">
<img class="avatar-pic pp20 mr-1">
<img class="avatar-hat profile-pic-20-hat hat" loading="lazy">
</div>
<a href="" class="font-weight-bold text-black userlink" target="_blank"></a>
<span class="text-black time ml-1 mb-3 d-none text-center">just now</span>
<div class="cdiv">
<div class="d-none quotes" style="font-size:10px">
<a class="QuotedMessageLink">
<i class="fas fa-reply"></i> <span class="text-primary">@<span class="QuotedUser"></span></span>: <em class="QuotedMessage"></em>
</a>
</div>
<span class="chat-message text-black text-break"></span>
<span class="text d-none"></span>
{% if v.admin_level > 1 %}
<i class="btn del fas fa-trash-alt"></i>
<i class="btn d-none del delmsg fas fa-trash-alt text-danger" data-nonce="{{g.nonce}}" data-onclick="del(this)"></i>
{% endif %}
<i class="quote btn fas fa-reply" data-nonce="{{g.nonce}}" data-onclick="quote(this)"></i>
</div>
</div>
</div>
</div>
</div>
<div id="shrink">
<div id="chat-window" class="container pl-0 py-0">
</div>
<div class="mt-3"></div>
<div id="quotes" class="mt-3 ml-3 mb-2 d-none" style="font-size:10px">
<a id="QuotedMessageLink">
<i class="fas fa-reply"></i> <span class="text-primary">@<span id="QuotedUser"></span></span>: <em id="QuotedMessage"></em>
<button type="button" id="cancel" class="btn btn-secondary">Cancel</button>
<input hidden id="quotes_id">
</a>
</div>
<div id='message' class="d-none position-relative form-group d-flex">
<div class="position-absolute text-muted text-small ml-1" style="bottom: -1.5rem; line-height: 1;">
<span id="typing-indicator"></span>
<span id="loading-indicator" class="d-none"></span>
</div>
<i class="btn btn-secondary fas fa-smile-beam my-auto" style="font-size:1.3rem!important" data-nonce="{{g.nonce}}" data-onclick="loadEmojis('input-text')"></i>
<label class="btn btn-secondary format my-auto ml-2">
<div id="filename" class="mr-4" style="font-size:10px"><i class="fas fa-image" style="font-size:1.3rem!important"></i></div>
<input autocomplete="off" id="file" accept="image/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} data-nonce="{{g.nonce}}" data-onchange="changename('filename','file')" hidden>
</label>
<textarea data-nonce="{{g.nonce}}" data-onclick="scroll_chat()" id="input-text" minlength="1" maxlength="{% if SITE == 'rdrama.net' %}200{% else %}1000{% endif %}" style="font-size:16px!important" class="form-control" placeholder="Message" autocomplete="off" autofocus rows="1"></textarea>
<i id="chatsend" data-nonce="{{g.nonce}}" data-onclick="send()" class="btn btn-secondary fas fa-reply ml-3 my-auto" type="submit" data-nonce="{{g.nonce}}" data-onclick="disable(this)" style="transform:rotateY(180deg);font-size:1.3rem!important"></i>
</div>
</div>
</div>
<div class="col text-left d-none d-lg-block pt-3" style="max-width:300px">
<h5>Users Online</h5>
<div id="online" class="mt-3"></div>
</div>
</div>
<input id="vid" type="hidden" value="{{v.id}}">
<input id="vusername" type="hidden" value="{{v.username}}">
<input id="site_name" type="hidden" value="{{SITE_NAME}}">
<input id="slurreplacer" type="hidden" value="{{v.slurreplacer}}">
<script defer src="{{'js/vendor/socketio.js' | asset}}"></script>
<script defer src="{{'js/chat.js' | asset}}"></script>
<script defer src="{{'js/chat_done.js' | asset}}"></script>
<script defer src="{{'js/vendor/lozad.js' | asset}}"></script>
<script defer src="{{'js/vendor/lite-youtube.js' | asset}}"></script>
{% endblock %}

View File

@ -322,9 +322,11 @@
</li>
{% endif %}
<li class="mt-3">
{% if has_sidebar %}
{% set sidebar = "sidebar_" ~ SITE_NAME ~ ".html" %}
{% include sidebar %}
{% if request.path.endswith("/chat") %}
<h5 class="ml-3">Users Online</h5>
<div id="online3" class="col text-left d-lg-none bg-white mb-6" style="max-width:300px"></div>
{% elif has_sidebar %}
{% include "sidebar_" ~ SITE_NAME ~ ".html" %}
{% endif %}
</li>
</ul>

View File

@ -95,7 +95,6 @@
{% macro stylesheets(include_user_css) %}
<link rel="stylesheet" href="{{'css/main.css' | asset}}">
<link id="favicon" rel="icon" type="image/webp" href="/icon.webp?v=1">
{% if v %}
{% if v.agendaposter %}
<link rel="stylesheet" href="{{('css/agendaposter.css') | asset}}">
@ -135,7 +134,7 @@
{% endif %}
{% if request.path.endswith('/chat') %}
<link rel="stylesheet" href="{{'css/chat_done.css' | asset}}">
<link rel="stylesheet" href="{{'css/chat.css' | asset}}">
{% endif %}
{% endmacro %}

View File

@ -44,6 +44,10 @@ server {
alias /images/;
include includes/serve-static;
}
location /chat_images/ {
alias /chat_images/;
include includes/serve-static;
}
location /videos/ {
alias /videos/;
include includes/serve-static;

View File

@ -37,6 +37,7 @@ psql -U postgres -f seed-db.sql postgres
pip3 install -r requirements.txt
mkdir /images
mkdir /chat_images
mkdir /songs
mkdir /videos
mkdir /audio