Change chat channel to node websocket
This commit is contained in:
parent
aeda77b924
commit
231687e081
12 changed files with 308 additions and 280 deletions
|
@ -1,12 +0,0 @@
|
|||
"""
|
||||
ASGI entrypoint. Configures Django and then runs the application
|
||||
defined in the ASGI_APPLICATION setting.
|
||||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
from channels.routing import get_default_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dmoj.settings")
|
||||
django.setup()
|
||||
application = get_default_application()
|
|
@ -1,8 +0,0 @@
|
|||
from django.urls import re_path
|
||||
|
||||
from . import consumers
|
||||
|
||||
ASGI_APPLICATION = "chat_box.routing.application"
|
||||
websocket_urlpatterns = [
|
||||
re_path(r'ws/chat/', consumers.ChatConsumer),
|
||||
]
|
|
@ -1,26 +1,119 @@
|
|||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import ListView
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
||||
from django.core.paginator import Paginator
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import render
|
||||
from django.forms.models import model_to_dict
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import datetime
|
||||
|
||||
from judge import event_poster as event
|
||||
from judge.jinja2.gravatar import gravatar
|
||||
from .models import Message, Profile
|
||||
import json
|
||||
|
||||
def format_messages(messages):
|
||||
msg_list = [{
|
||||
'time': msg.time,
|
||||
'author': msg.author,
|
||||
'body': msg.body,
|
||||
'image': gravatar(msg.author, 32),
|
||||
'id': msg.id,
|
||||
'css_class': msg.author.css_class,
|
||||
} for msg in messages]
|
||||
return json.dumps(msg_list, default=str)
|
||||
|
||||
class ChatView(ListView):
|
||||
context_object_name = 'message'
|
||||
template_name = 'chat/chat.html'
|
||||
title = _('Chat Box')
|
||||
paginate_by = 50
|
||||
messages = Message.objects.filter(hidden=False)
|
||||
paginator = Paginator(messages, paginate_by)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.messages
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
page = request.GET.get('page')
|
||||
if page == None:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
cur_page = self.paginator.get_page(page)
|
||||
|
||||
return render(request, 'chat/message_list.html', {
|
||||
'object_list': cur_page.object_list,
|
||||
})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['title'] = self.title
|
||||
context['last_msg'] = event.last()
|
||||
context['online_users'] = get_user_online_status()
|
||||
context['admin_status'] = get_admin_online_status()
|
||||
context['today'] = timezone.now().strftime("%d-%m-%Y")
|
||||
return context
|
||||
|
||||
|
||||
def delete_message(request):
|
||||
ret = {'delete': 'done'}
|
||||
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(ret)
|
||||
|
||||
if request.user.is_staff:
|
||||
try:
|
||||
messid = int(request.POST.get('message'))
|
||||
mess = Message.objects.get(id=messid)
|
||||
except:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
mess.hidden = True
|
||||
mess.save()
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
|
||||
@login_required
|
||||
def post_message(request):
|
||||
ret = {'msg': 'posted'}
|
||||
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(ret)
|
||||
|
||||
new_message = Message(author=request.profile,
|
||||
body=request.POST['body'])
|
||||
new_message.save()
|
||||
|
||||
event.post('chat', {
|
||||
'type': 'new_message',
|
||||
'message': new_message.id,
|
||||
})
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
@login_required
|
||||
def chat_message_ajax(request):
|
||||
if request.method != 'GET':
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
message_id = request.GET['message']
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
message = Message.objects.filter(hidden=False).get(id=message_id)
|
||||
except Message.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
return render(request, 'chat/message.html', {
|
||||
'message': message,
|
||||
})
|
||||
|
||||
|
||||
def get_user_online_status():
|
||||
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
|
||||
return Profile.objects \
|
||||
.filter(display_rank='user',
|
||||
last_access__gte = last_five_minutes)\
|
||||
.order_by('-rating')
|
||||
|
||||
|
||||
def get_admin_online_status():
|
||||
all_admin = Profile.objects.filter(display_rank='admin')
|
||||
|
@ -35,63 +128,10 @@ def get_admin_online_status():
|
|||
|
||||
return ret
|
||||
|
||||
class ChatView(ListView):
|
||||
model = Message
|
||||
context_object_name = 'message'
|
||||
template_name = 'chat/chat.html'
|
||||
title = _('Chat Box')
|
||||
paginate_by = 50
|
||||
paginator = Paginator(Message.objects.filter(hidden=False), paginate_by)
|
||||
|
||||
def get_queryset(self):
|
||||
return Message.objects.filter(hidden=False)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
page = request.GET.get('page')
|
||||
if (page == None):
|
||||
# return render(request, 'chat/chat.html', {'message': format_messages(Message.objects.all())})
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
cur_page = self.paginator.get_page(page)
|
||||
return HttpResponse(format_messages(cur_page.object_list))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# hard code, should be fixed later
|
||||
address = f'{self.request.get_host()}/ws/chat/'
|
||||
if self.request.is_secure():
|
||||
context['ws_address'] = f'wss://{address}'
|
||||
else:
|
||||
context['ws_address'] = f'ws://{address}'
|
||||
|
||||
context['title'] = self.title
|
||||
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
|
||||
context['online_users'] = Profile.objects \
|
||||
.filter(display_rank='user',
|
||||
last_access__gte = last_five_minutes)\
|
||||
.order_by('-rating')
|
||||
context['admin_status'] = get_admin_online_status()
|
||||
return context
|
||||
|
||||
def delete_message(request):
|
||||
ret = {'delete': 'done'}
|
||||
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(ret)
|
||||
|
||||
if request.user.is_staff:
|
||||
messid = int(request.POST.get('messid'))
|
||||
all_mess = Message.objects.all()
|
||||
|
||||
for mess in all_mess:
|
||||
if mess.id == messid:
|
||||
mess.hidden = True
|
||||
mess.save()
|
||||
new_elt = {'time': mess.time, 'content': mess.body}
|
||||
ret = new_elt
|
||||
break
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
return JsonResponse(ret)
|
||||
@login_required
|
||||
def online_status_ajax(request):
|
||||
return render(request, 'chat/online_status.html', {
|
||||
'online_users': get_user_online_status(),
|
||||
'admin_status': get_admin_online_status(),
|
||||
})
|
|
@ -1,12 +0,0 @@
|
|||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
import chat_box.routing
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
# (http->django views is added by default)
|
||||
'websocket': AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
chat_box.routing.websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
})
|
|
@ -243,7 +243,6 @@ INSTALLED_APPS += (
|
|||
'impersonate',
|
||||
'django_jinja',
|
||||
'chat_box',
|
||||
'channels',
|
||||
'newsletter',
|
||||
)
|
||||
|
||||
|
@ -513,17 +512,6 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
|||
|
||||
MESSAGES_TO_LOAD = 15
|
||||
|
||||
ASGI_APPLICATION = 'dmoj.routing.application'
|
||||
CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
'CONFIG': {
|
||||
"hosts": [('0.0.0.0', 6379)],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
NEWSLETTER_CONFIRM_EMAIL = False
|
||||
|
||||
# Amount of seconds to wait between each email. Here 100ms is used.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from chat_box.views import ChatView, delete_message
|
||||
from chat_box.views import ChatView, delete_message, post_message, chat_message_ajax, online_status_ajax
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
|
@ -372,8 +372,10 @@ urlpatterns = [
|
|||
url(r'^$',
|
||||
login_required(ChatView.as_view()),
|
||||
name='chat'),
|
||||
url(r'^delete/$', delete_message, name='delete_message')
|
||||
|
||||
url(r'^delete/$', delete_message, name='delete_chat_message'),
|
||||
url(r'^post/$', post_message, name='post_chat_message'),
|
||||
url(r'^ajax$', chat_message_ajax, name='chat_message_ajax'),
|
||||
url(r'^online_status/ajax$', online_status_ajax, name='online_status_ajax')
|
||||
])),
|
||||
|
||||
url(r'^notifications/',
|
||||
|
|
|
@ -30,9 +30,6 @@ packaging
|
|||
celery
|
||||
-e git://github.com/DMOJ/ansi2html.git#egg=ansi2html
|
||||
sqlparse
|
||||
channels==2.4.0
|
||||
channels-redis==2.4.2
|
||||
docker
|
||||
django-newsletter
|
||||
netaddr
|
||||
redis
|
||||
|
|
|
@ -51,11 +51,14 @@
|
|||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.content-message {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.content-message p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#chat-area {
|
||||
height: 85vh;
|
||||
|
|
|
@ -4,90 +4,30 @@
|
|||
{% block title %} {{_('Chat Box')}} {% endblock %}
|
||||
{% block js_media %}
|
||||
|
||||
<script type="text/javascript">
|
||||
// change ws to wss if using HTTPS
|
||||
var chatSocket = new WebSocket( "{{ws_address}}" );
|
||||
</script>
|
||||
<script type="text/javascript" src="{{ static('mathjax_config.js') }}"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML"></script>
|
||||
<script type="text/javascript" src="{{ static('event.js') }}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
let currentPage = 1;
|
||||
window.currentPage = 1;
|
||||
|
||||
function load_page(page) {
|
||||
$.get('?page=' + page)
|
||||
.fail(function() {
|
||||
console.log("Fail to load page " + page);
|
||||
})
|
||||
.done(function(data) {
|
||||
setTimeout(function() {
|
||||
let container = $('#chat-box');
|
||||
let lastMsgPos = scrollTopOfBottom(container)
|
||||
|
||||
$('#loader').hide();
|
||||
$('#chat-log').prepend(data);
|
||||
remove_day_if_today();
|
||||
|
||||
chatSocket.onmessage = function(e) {
|
||||
let data = JSON.parse(e.data);
|
||||
data = data['message'];
|
||||
loadMessage(data['body'],
|
||||
data['author'],
|
||||
data['time'],
|
||||
data['id'],
|
||||
data['image'],
|
||||
data['css_class'],
|
||||
true);
|
||||
// console.log(data);
|
||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||
};
|
||||
|
||||
function encodeHTML(content) {
|
||||
return content.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
|
||||
return '&#'+i.charCodeAt(0)+';';
|
||||
});
|
||||
container.scrollTop(scrollTopOfBottom(container) - lastMsgPos);
|
||||
}, 500);
|
||||
})
|
||||
}
|
||||
const datesAreOnSameDay = (first, second) =>
|
||||
first.getFullYear() === second.getFullYear() &&
|
||||
first.getMonth() === second.getMonth() &&
|
||||
first.getDate() === second.getDate();
|
||||
|
||||
function loadMessage(content, user, time, messid, image, css_class, isNew) {
|
||||
// if (isNew) content = encodeHTML(content)
|
||||
time = new Date(time);
|
||||
if (datesAreOnSameDay(time, new Date())) {
|
||||
time = moment(time).format("HH:mm");
|
||||
}
|
||||
else {
|
||||
time = moment(time).format("HH:mm DD-MM-YYYY");
|
||||
}
|
||||
content = encodeHTML(content);
|
||||
li = `<li class="message">
|
||||
<img src="${image}" class="profile-pic">
|
||||
<div class="body-message">
|
||||
<div class="user-time">
|
||||
<span class="${css_class}">
|
||||
<a href="{{ url('user_page') }}/${user}">
|
||||
${user}
|
||||
</a>
|
||||
</span>
|
||||
<span class="time">${time}</span>
|
||||
{% if request.user.is_staff %}
|
||||
<a class="chatbtn_remove_mess" style="color:red; cursor: pointer;" data-messtime="${time}" data-author="${user}" data-messid="${messid}">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="content-message">${content} </span>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</li>`
|
||||
ul = $('#chat-log')
|
||||
if (isNew) {
|
||||
ul.append(li)
|
||||
}
|
||||
else {
|
||||
ul.prepend(li)
|
||||
}
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
||||
}
|
||||
|
||||
(function init_chatlog() {
|
||||
ul = $('#chat-log')
|
||||
{% autoescape on %}
|
||||
{% for msg in message %}
|
||||
loadMessage("{{msg.body|safe|escapejs}}", `{{msg.author}}`, `{{msg.time}}`, `{{msg.id}}`, `{{gravatar(msg.author, 32)}}`,`{{msg.author.css_class}}`);
|
||||
{% endfor %}
|
||||
{% endautoescape %}
|
||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||
})()
|
||||
|
||||
function scrollTopOfBottom(container) {
|
||||
return container[0].scrollHeight - container.innerHeight()
|
||||
|
@ -99,40 +39,71 @@
|
|||
if (currentPage < {{paginator.num_pages}}) {
|
||||
currentPage++;
|
||||
loader.show();
|
||||
$.ajax({
|
||||
url: `{{request.path}}?page=${currentPage}`,
|
||||
success: function(data) {
|
||||
let lastMsg = $('.message:first')
|
||||
let lastMsgPos = scrollTopOfBottom(container)
|
||||
|
||||
data = JSON.parse(data)
|
||||
setTimeout( () => {
|
||||
for (msg of data) {
|
||||
loadMessage(msg.body, msg.author, msg.time, msg.id, msg.image, msg.css_class)
|
||||
}
|
||||
|
||||
loader.hide()
|
||||
|
||||
// scroll to last msg
|
||||
container.scrollTop(
|
||||
scrollTopOfBottom(container) - lastMsgPos
|
||||
)
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
load_page(currentPage);
|
||||
}
|
||||
}
|
||||
})}
|
||||
|
||||
function remove_day_if_today() {
|
||||
$('.message_date').each(function() {
|
||||
sent_date = $(this).html()
|
||||
console.log(sent_date);
|
||||
if (sent_date === "{{today}}") {
|
||||
$(this).hide();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.load_dynamic_update = function (last_msg) {
|
||||
return new EventReceiver(
|
||||
"{{ EVENT_DAEMON_LOCATION }}", "{{ EVENT_DAEMON_POLL_LOCATION }}",
|
||||
['chat'], last_msg, function (message) {
|
||||
switch (message.type) {
|
||||
case 'new_message':
|
||||
add_new_message(message.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function add_new_message(message) {
|
||||
// console.log(message);
|
||||
$.get({
|
||||
url: "{{ url('chat_message_ajax') }}",
|
||||
data: {
|
||||
message: message,
|
||||
},
|
||||
success: function (data) {
|
||||
$('#chat-log').append($(data));
|
||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight)
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
||||
},
|
||||
error: function (data) {
|
||||
if (data.status === 403)
|
||||
console.log('No right to see: ' + message);
|
||||
else {
|
||||
console.log('Could not load chat message:');
|
||||
console.log(data.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('#loader').hide();
|
||||
|
||||
scrollContainer($('#chat-box'), $('#loader'))
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
$(document).on("click", ".chatbtn_remove_mess", function() {
|
||||
var elt = $(this);
|
||||
$.ajax({
|
||||
url: 'delete/',
|
||||
url: "{{ url('delete_chat_message') }}",
|
||||
type: 'post',
|
||||
data: elt.data(),
|
||||
data: {
|
||||
message: elt.attr('value'),
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data){
|
||||
elt.closest('li').hide();
|
||||
|
@ -147,24 +118,22 @@
|
|||
$("#chat-submit").click(function() {
|
||||
if ($("#chat-input").val().trim()) {
|
||||
let body = $('#chat-input').val().trim();
|
||||
let img = '{{ gravatar(request.user, 32) }}'
|
||||
let img = '{{ gravatar(request.user, 32) }}';
|
||||
|
||||
message = {
|
||||
'body': body,
|
||||
}
|
||||
|
||||
chatSocket.send(JSON.stringify({
|
||||
'message': message
|
||||
}));
|
||||
body: body,
|
||||
};
|
||||
|
||||
$.post("{{ url('post_chat_message') }}", message)
|
||||
.fail(function(res) {
|
||||
console.log('Fail to send message');
|
||||
})
|
||||
.done(function(res, status) {
|
||||
$('#chat-input').val('').focus();
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
chatSocket.onclose = function(e) {
|
||||
console.error('Chat socket closed unexpectedly');
|
||||
};
|
||||
|
||||
$("#chat-log").change(function() {
|
||||
$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
|
||||
});
|
||||
|
@ -196,9 +165,6 @@
|
|||
return true
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.chat-right-panel').hide();
|
||||
$('#chat-tab').find('a').click(function (e) {
|
||||
e.preventDefault();
|
||||
|
@ -214,17 +180,46 @@
|
|||
$('.chat-left-panel').hide();
|
||||
$('.chat-right-panel').show();
|
||||
});
|
||||
|
||||
$('#refresh-button').on('click', function() {
|
||||
$.get("{{url('online_status_ajax')}}")
|
||||
.fail(function() {
|
||||
console.log("Fail to get online status");
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status == 403) {
|
||||
console.log("Fail to retrieve data");
|
||||
}
|
||||
else {
|
||||
$('#chat-online-content').html(data);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||
remove_day_if_today();
|
||||
load_dynamic_update({{last_msg}});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock js_media %}
|
||||
|
||||
{% block media %}
|
||||
<style>
|
||||
#content {
|
||||
<style>
|
||||
#content {
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
#refresh-button {
|
||||
padding: 0 0.1em 0 0.1em;
|
||||
margin: 0.1em;
|
||||
border-radius: 0.1em;
|
||||
background: goldenrod;
|
||||
}
|
||||
#refresh-button:hover {
|
||||
background: lightgreen;
|
||||
color: grey !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock media %}
|
||||
|
||||
{% block body %}
|
||||
|
@ -244,6 +239,7 @@
|
|||
<div id="chat-box">
|
||||
<img src="http://opengraphicdesign.com/wp-content/uploads/2009/01/loader64.gif" id="loader">
|
||||
<ul id="chat-log">
|
||||
{% include 'chat/message_list.html' %}
|
||||
</ul>
|
||||
</div>
|
||||
<textarea id="chat-input" placeholder="{{_('Enter your message')}}"></textarea>
|
||||
|
@ -252,33 +248,10 @@
|
|||
<div id="chat-online" class="chat-right-panel sidebox">
|
||||
<h3>
|
||||
{{_('Online Users')}}
|
||||
<i class="fa fa-wifi"></i>
|
||||
<button id="refresh-button" class="fa fa-rotate-right" title="{{_('Refresh')}}"></button>
|
||||
</h3>
|
||||
<ul id="chat-online-content">
|
||||
<h4>{{_('Admins')}}: </h4>
|
||||
<hr/>
|
||||
{% for user in admin_status %}
|
||||
<li style="padding-left: 1em">
|
||||
{% if user.is_online %}
|
||||
<span class="green-dot"></span>
|
||||
{% else %}
|
||||
<span class="red-dot"></span>
|
||||
{% endif %}
|
||||
<span style="padding-left:0.25em">
|
||||
{{ link_user(user.user) }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<h4 style="margin-top:1em;">{{_('Users')}}: </h4>
|
||||
<hr/>
|
||||
{% for user in online_users %}
|
||||
<li style="padding-left: 1em">
|
||||
<span class="green-dot"></span>
|
||||
<span style="padding-left:0.25em">
|
||||
{{ link_user(user.user) }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% include "chat/online_status.html" %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
25
templates/chat/message.html
Normal file
25
templates/chat/message.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<li class="message" id="message-{{ message.id }}">
|
||||
<img src="{{ gravatar(message.author, 32) }}" class="profile-pic">
|
||||
<div class="body-message">
|
||||
<div class="user-time">
|
||||
<span class="username {{ message.author.css_class }}">
|
||||
<a href="{{ url('user_page', message.author.user.username) }}">
|
||||
{{message.author}}
|
||||
</a>
|
||||
</span>
|
||||
<span class="time">
|
||||
{{ message.time|date('TIME_FORMAT') }}
|
||||
<span class="message_date">{{ message.time|date('d-m-Y') }}</span>
|
||||
</span>
|
||||
{% if request.user.is_staff %}
|
||||
<a class="chatbtn_remove_mess" value="{{message.id}}" style="color:red; cursor: pointer;">
|
||||
Delete
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="content-message">
|
||||
{{message.body | markdown('comment', MATH_ENGINE)|reference|str|safe }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</li>
|
8
templates/chat/message_list.html
Normal file
8
templates/chat/message_list.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% for message in object_list | reverse%}
|
||||
{% include "chat/message.html" %}
|
||||
{% endfor %}
|
||||
|
||||
{% if REQUIRE_JAX %}
|
||||
{% include "mathjax-load.html" %}
|
||||
{% endif %}
|
||||
{% include "comments/math.html" %}
|
24
templates/chat/online_status.html
Normal file
24
templates/chat/online_status.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<h4>{{_('Admins')}}: </h4>
|
||||
<hr/>
|
||||
{% for user in admin_status %}
|
||||
<li style="padding-left: 1em">
|
||||
{% if user.is_online %}
|
||||
<span class="green-dot"></span>
|
||||
{% else %}
|
||||
<span class="red-dot"></span>
|
||||
{% endif %}
|
||||
<span style="padding-left:0.25em">
|
||||
{{ link_user(user.user) }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<h4 style="margin-top:1em;">{{_('Users')}}: </h4>
|
||||
<hr/>
|
||||
{% for user in online_users %}
|
||||
<li style="padding-left: 1em">
|
||||
<span class="green-dot"></span>
|
||||
<span style="padding-left:0.25em">
|
||||
{{ link_user(user.user) }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
Loading…
Reference in a new issue