Add direct message
This commit is contained in:
parent
259cb95b43
commit
2f8ef1b524
20 changed files with 1066 additions and 195 deletions
28
chat_box/migrations/0005_auto_20211011_0714.py
Normal file
28
chat_box/migrations/0005_auto_20211011_0714.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-10-11 00:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0116_auto_20211011_0645'),
|
||||||
|
('chat_box', '0004_auto_20200505_2336'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Room',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('user_one', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_one', to='judge.Profile', verbose_name='user 1')),
|
||||||
|
('user_two', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_two', to='judge.Profile', verbose_name='user 2')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='room',
|
||||||
|
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id'),
|
||||||
|
),
|
||||||
|
]
|
24
chat_box/migrations/0006_userroom.py
Normal file
24
chat_box/migrations/0006_userroom.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-11-12 05:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0116_auto_20211011_0645'),
|
||||||
|
('chat_box', '0005_auto_20211011_0714'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserRoom',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_seen', models.DateTimeField(verbose_name='last seen')),
|
||||||
|
('room', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='chat_box.Room', verbose_name='room id')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='user')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
18
chat_box/migrations/0007_auto_20211112_1255.py
Normal file
18
chat_box/migrations/0007_auto_20211112_1255.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-11-12 05:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('chat_box', '0006_userroom'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userroom',
|
||||||
|
name='last_seen',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='last seen'),
|
||||||
|
),
|
||||||
|
]
|
23
chat_box/migrations/0008_ignore.py
Normal file
23
chat_box/migrations/0008_ignore.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-11-18 10:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0116_auto_20211011_0645'),
|
||||||
|
('chat_box', '0007_auto_20211112_1255'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Ignore',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ignored_users', models.ManyToManyField(to='judge.Profile')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ignored_chat_users', to='judge.Profile', verbose_name='user')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,12 +8,23 @@ from judge.models.profile import Profile
|
||||||
|
|
||||||
__all__ = ['Message']
|
__all__ = ['Message']
|
||||||
|
|
||||||
|
class Room(models.Model):
|
||||||
|
user_one = models.ForeignKey(Profile, related_name="user_one", verbose_name='user 1', on_delete=CASCADE)
|
||||||
|
user_two = models.ForeignKey(Profile, related_name="user_two", verbose_name='user 2', on_delete=CASCADE)
|
||||||
|
|
||||||
|
def contain(self, profile):
|
||||||
|
return self.user_one == profile or self.user_two == profile
|
||||||
|
def other_user(self, profile):
|
||||||
|
return self.user_one if profile == self.user_two else self.user_two
|
||||||
|
def users(self):
|
||||||
|
return [self.user_one, self.user_two]
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
author = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
|
author = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
|
||||||
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
|
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
|
||||||
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
|
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
|
||||||
hidden = models.BooleanField(verbose_name='is hidden', default=False)
|
hidden = models.BooleanField(verbose_name='is hidden', default=False)
|
||||||
|
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_message = self.id
|
new_message = self.id
|
||||||
|
@ -25,3 +36,46 @@ class Message(models.Model):
|
||||||
verbose_name = 'message'
|
verbose_name = 'message'
|
||||||
verbose_name_plural = 'messages'
|
verbose_name_plural = 'messages'
|
||||||
ordering = ('-time',)
|
ordering = ('-time',)
|
||||||
|
|
||||||
|
class UserRoom(models.Model):
|
||||||
|
user = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=CASCADE)
|
||||||
|
room = models.ForeignKey(Room, verbose_name='room id', on_delete=CASCADE, default=None, null=True)
|
||||||
|
last_seen = models.DateTimeField(verbose_name=_('last seen'), auto_now_add=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Ignore(models.Model):
|
||||||
|
user = models.ForeignKey(Profile, related_name="ignored_chat_users", verbose_name=_('user'), on_delete=CASCADE)
|
||||||
|
ignored_users = models.ManyToManyField(Profile)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_ignored(self, current_user, new_friend):
|
||||||
|
try:
|
||||||
|
return current_user.ignored_chat_users.get().ignored_users \
|
||||||
|
.filter(id=new_friend.id).exists()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ignored_users(self, user):
|
||||||
|
return self.objects.get(user=user).ignored_users.all()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_ignore(self, current_user, friend):
|
||||||
|
ignore, created = self.objects.get_or_create(
|
||||||
|
user = current_user
|
||||||
|
)
|
||||||
|
ignore.ignored_users.add(friend)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_ignore(self, current_user, friend):
|
||||||
|
ignore, created = self.objects.get_or_create(
|
||||||
|
user = current_user
|
||||||
|
)
|
||||||
|
ignore.ignored_users.remove(friend)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_ignore(self, current_user, friend):
|
||||||
|
if (self.is_ignored(current_user, friend)):
|
||||||
|
self.remove_ignore(current_user, friend)
|
||||||
|
else:
|
||||||
|
self.add_ignore(current_user, friend)
|
18
chat_box/utils.py
Normal file
18
chat_box/utils.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
secret_key = settings.CHAT_SECRET_KEY
|
||||||
|
fernet = Fernet(secret_key)
|
||||||
|
|
||||||
|
def encrypt_url(creator_id, other_id):
|
||||||
|
message = str(creator_id) + '_' + str(other_id)
|
||||||
|
return fernet.encrypt(message.encode()).decode()
|
||||||
|
|
||||||
|
def decrypt_url(message_encrypted):
|
||||||
|
try:
|
||||||
|
dec_message = fernet.decrypt(message_encrypted.encode()).decode()
|
||||||
|
creator_id, other_id = dec_message.split('_')
|
||||||
|
return int(creator_id), int(other_id)
|
||||||
|
except Exception as e:
|
||||||
|
return None, None
|
|
@ -1,42 +1,72 @@
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect, HttpResponseRedirect
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from django.db.models import Case, BooleanField
|
from django.db.models import Case, BooleanField, When, Q, Subquery, OuterRef, Exists, Count, IntegerField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from judge import event_poster as event
|
from judge import event_poster as event
|
||||||
from judge.jinja2.gravatar import gravatar
|
from judge.jinja2.gravatar import gravatar
|
||||||
from .models import Message, Profile
|
from judge.models import Friend
|
||||||
|
|
||||||
|
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
|
||||||
|
from chat_box.utils import encrypt_url, decrypt_url
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ChatView(ListView):
|
class ChatView(ListView):
|
||||||
context_object_name = 'message'
|
context_object_name = 'message'
|
||||||
template_name = 'chat/chat.html'
|
template_name = 'chat/chat.html'
|
||||||
title = _('Chat Box')
|
title = _('Chat Box')
|
||||||
paginate_by = 50
|
|
||||||
messages = Message.objects.filter(hidden=False)
|
def __init__(self):
|
||||||
paginator = Paginator(messages, paginate_by)
|
super().__init__()
|
||||||
|
self.room_id = None
|
||||||
|
self.room = None
|
||||||
|
self.paginate_by = 50
|
||||||
|
self.messages = None
|
||||||
|
self.paginator = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.messages
|
return self.messages
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
request_room = kwargs['room_id']
|
||||||
page = request.GET.get('page')
|
page = request.GET.get('page')
|
||||||
|
|
||||||
|
if request_room:
|
||||||
|
try:
|
||||||
|
self.room = Room.objects.get(id=request_room)
|
||||||
|
if not can_access_room(request, self.room):
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
except Room.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
else:
|
||||||
|
request_room = None
|
||||||
|
|
||||||
|
if request_room != self.room_id or not self.messages:
|
||||||
|
self.room_id = request_room
|
||||||
|
self.messages = Message.objects.filter(hidden=False, room=self.room_id)
|
||||||
|
self.paginator = Paginator(self.messages, self.paginate_by)
|
||||||
|
|
||||||
if page == None:
|
if page == None:
|
||||||
|
update_last_seen(request, **kwargs)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
cur_page = self.paginator.get_page(page)
|
cur_page = self.paginator.get_page(page)
|
||||||
|
|
||||||
return render(request, 'chat/message_list.html', {
|
return render(request, 'chat/message_list.html', {
|
||||||
'object_list': cur_page.object_list,
|
'object_list': cur_page.object_list,
|
||||||
|
'num_pages': self.paginator.num_pages
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -45,7 +75,22 @@ class ChatView(ListView):
|
||||||
context['title'] = self.title
|
context['title'] = self.title
|
||||||
context['last_msg'] = event.last()
|
context['last_msg'] = event.last()
|
||||||
context['status_sections'] = get_status_context(self.request)
|
context['status_sections'] = get_status_context(self.request)
|
||||||
context['today'] = timezone.now().strftime("%d-%m-%Y")
|
context['room'] = self.room_id
|
||||||
|
context['unread_count_lobby'] = get_unread_count(None, self.request.profile)
|
||||||
|
if self.room:
|
||||||
|
users_room = [self.room.user_one, self.room.user_two]
|
||||||
|
users_room.remove(self.request.profile)
|
||||||
|
context['other_user'] = users_room[0]
|
||||||
|
context['other_online'] = get_user_online_status(context['other_user'])
|
||||||
|
context['is_ignored'] = Ignore.is_ignored(self.request.profile, context['other_user'])
|
||||||
|
else:
|
||||||
|
context['online_count'] = get_online_count()
|
||||||
|
context['message_template'] = {
|
||||||
|
'author': self.request.profile,
|
||||||
|
'id': '$id',
|
||||||
|
'time': timezone.now(),
|
||||||
|
'body': '$body'
|
||||||
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,21 +118,46 @@ def delete_message(request):
|
||||||
@login_required
|
@login_required
|
||||||
def post_message(request):
|
def post_message(request):
|
||||||
ret = {'msg': 'posted'}
|
ret = {'msg': 'posted'}
|
||||||
|
if request.method != 'POST':
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
if request.method == 'GET':
|
room = None
|
||||||
return JsonResponse(ret)
|
if request.POST['room']:
|
||||||
|
room = Room.objects.get(id=request.POST['room'])
|
||||||
|
|
||||||
|
if not can_access_room(request, room) or request.profile.mute:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
new_message = Message(author=request.profile,
|
new_message = Message(author=request.profile,
|
||||||
body=request.POST['body'])
|
body=request.POST['body'],
|
||||||
|
room=room)
|
||||||
new_message.save()
|
new_message.save()
|
||||||
|
|
||||||
event.post('chat', {
|
if not room:
|
||||||
'type': 'new_message',
|
event.post('chat_lobby', {
|
||||||
'message': new_message.id,
|
'type': 'lobby',
|
||||||
})
|
'author_id': request.profile.id,
|
||||||
|
'message': new_message.id,
|
||||||
|
'room': 'None',
|
||||||
|
'tmp_id': request.POST.get('tmp_id')
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
for user in room.users():
|
||||||
|
event.post('chat_' + str(user.id), {
|
||||||
|
'type': 'private',
|
||||||
|
'author_id': request.profile.id,
|
||||||
|
'message': new_message.id,
|
||||||
|
'room': room.id,
|
||||||
|
'tmp_id': request.POST.get('tmp_id')
|
||||||
|
})
|
||||||
|
|
||||||
return JsonResponse(ret)
|
return JsonResponse(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def can_access_room(request, room):
|
||||||
|
return not room or room.user_one == request.profile or room.user_two == request.profile
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def chat_message_ajax(request):
|
def chat_message_ajax(request):
|
||||||
if request.method != 'GET':
|
if request.method != 'GET':
|
||||||
|
@ -100,6 +170,9 @@ def chat_message_ajax(request):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = Message.objects.filter(hidden=False).get(id=message_id)
|
message = Message.objects.filter(hidden=False).get(id=message_id)
|
||||||
|
room = message.room
|
||||||
|
if room and not room.contain(request.profile):
|
||||||
|
return HttpResponse('Unauthorized', status=401)
|
||||||
except Message.DoesNotExist:
|
except Message.DoesNotExist:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
return render(request, 'chat/message.html', {
|
return render(request, 'chat/message.html', {
|
||||||
|
@ -107,53 +180,156 @@ def chat_message_ajax(request):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_user_online_status():
|
@login_required
|
||||||
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
|
def update_last_seen(request, **kwargs):
|
||||||
return Profile.objects \
|
if 'room_id' in kwargs:
|
||||||
.filter(display_rank='user',
|
room_id = kwargs['room_id']
|
||||||
last_access__gte = last_five_minutes)\
|
elif request.method == 'GET':
|
||||||
.annotate(is_online=Case(default=True,output_field=BooleanField()))\
|
room_id = request.GET.get('room')
|
||||||
.order_by('-rating')
|
elif request.method == 'POST':
|
||||||
|
room_id = request.POST.get('room')
|
||||||
|
else:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
profile = request.profile
|
||||||
|
room = None
|
||||||
|
if room_id:
|
||||||
|
room = Room.objects.get(id=int(room_id))
|
||||||
|
except Room.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
if room and not room.contain(profile):
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
user_room, _ = UserRoom.objects.get_or_create(user=profile, room=room)
|
||||||
|
user_room.last_seen = timezone.now()
|
||||||
|
user_room.save()
|
||||||
|
|
||||||
|
return JsonResponse({'msg': 'updated'})
|
||||||
|
|
||||||
|
|
||||||
def get_admin_online_status():
|
def get_online_count():
|
||||||
all_admin = Profile.objects.filter(display_rank='admin')
|
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
|
||||||
last_five_minutes = timezone.now()-timezone.timedelta(minutes=5)
|
return Profile.objects.filter(last_access__gte=last_two_minutes).count()
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_online_status(user):
|
||||||
|
time_diff = timezone.now() - user.last_access
|
||||||
|
is_online = time_diff <= timezone.timedelta(minutes=2)
|
||||||
|
return is_online
|
||||||
|
|
||||||
|
|
||||||
|
def user_online_status_ajax(request):
|
||||||
|
if request.method != 'GET':
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
user_id = request.GET.get('user')
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
try:
|
||||||
|
user_id = int(user_id)
|
||||||
|
user = Profile.objects.get(id=user_id)
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
is_online = get_user_online_status(user)
|
||||||
|
return render(request, 'chat/user_online_status.html', {
|
||||||
|
'other_user': user,
|
||||||
|
'other_online': is_online,
|
||||||
|
'is_ignored': Ignore.is_ignored(request.profile, user)
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return render(request, 'chat/user_online_status.html', {
|
||||||
|
'online_count': get_online_count(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_online_status(request_user, queryset, rooms=None):
|
||||||
|
if not queryset:
|
||||||
|
return None
|
||||||
|
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
|
||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
for admin in all_admin:
|
if rooms:
|
||||||
|
unread_count = get_unread_count(rooms, request_user)
|
||||||
|
count = {}
|
||||||
|
for i in unread_count:
|
||||||
|
count[i['other_user']] = i['unread_count']
|
||||||
|
|
||||||
|
for user in queryset:
|
||||||
is_online = False
|
is_online = False
|
||||||
if (admin.last_access >= last_five_minutes):
|
if (user.last_access >= last_two_minutes):
|
||||||
is_online = True
|
is_online = True
|
||||||
ret.append({'user': admin, 'is_online': is_online})
|
user_dict = {'user': user, 'is_online': is_online}
|
||||||
|
if rooms and user.id in count:
|
||||||
|
user_dict['unread_count'] = count[user.id]
|
||||||
|
user_dict['url'] = encrypt_url(request_user.id, user.id)
|
||||||
|
ret.append(user_dict)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_status_context(request):
|
def get_status_context(request, include_ignored=False):
|
||||||
friend_list = request.profile.get_friends()
|
if include_ignored:
|
||||||
all_user_status = get_user_online_status()
|
ignored_users = Profile.objects.none()
|
||||||
friend_status = []
|
queryset = Profile.objects
|
||||||
user_status = []
|
else:
|
||||||
|
ignored_users = Ignore.get_ignored_users(request.profile)
|
||||||
|
queryset = Profile.objects.exclude(id__in=ignored_users)
|
||||||
|
|
||||||
for user in all_user_status:
|
last_two_minutes = timezone.now()-timezone.timedelta(minutes=2)
|
||||||
if user.username in friend_list:
|
recent_profile = Room.objects.filter(
|
||||||
friend_status.append(user)
|
Q(user_one=request.profile) | Q(user_two=request.profile)
|
||||||
else:
|
).annotate(
|
||||||
user_status.append(user)
|
last_msg_time=Subquery(
|
||||||
|
Message.objects.filter(room=OuterRef('pk')).values('time')[:1]
|
||||||
|
),
|
||||||
|
other_user=Case(
|
||||||
|
When(user_one=request.profile, then='user_two'),
|
||||||
|
default='user_one',
|
||||||
|
)
|
||||||
|
).filter(last_msg_time__isnull=False)\
|
||||||
|
.exclude(other_user__in=ignored_users)\
|
||||||
|
.order_by('-last_msg_time').values('other_user', 'id')[:20]
|
||||||
|
|
||||||
|
recent_profile_id = [str(i['other_user']) for i in recent_profile]
|
||||||
|
joined_id = ','.join(recent_profile_id)
|
||||||
|
recent_rooms = [int(i['id']) for i in recent_profile]
|
||||||
|
recent_list = None
|
||||||
|
if joined_id:
|
||||||
|
recent_list = Profile.objects.raw(
|
||||||
|
f'SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})')
|
||||||
|
|
||||||
|
friend_list = Friend.get_friend_profiles(request.profile).exclude(id__in=recent_profile_id)\
|
||||||
|
.exclude(id__in=ignored_users)\
|
||||||
|
.order_by('-last_access')
|
||||||
|
admin_list = queryset.filter(display_rank='admin')\
|
||||||
|
.exclude(id__in=friend_list).exclude(id__in=recent_profile_id)
|
||||||
|
all_user_status = queryset\
|
||||||
|
.filter(display_rank='user',
|
||||||
|
last_access__gte = last_two_minutes)\
|
||||||
|
.annotate(is_online=Case(default=True,output_field=BooleanField()))\
|
||||||
|
.order_by('-rating').exclude(id__in=friend_list).exclude(id__in=admin_list)\
|
||||||
|
.exclude(id__in=recent_profile_id)[:30]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'title': 'Admins',
|
'title': 'Recent',
|
||||||
'user_list': get_admin_online_status(),
|
'user_list': get_online_status(request.profile, recent_list, recent_rooms),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Following',
|
'title': 'Following',
|
||||||
'user_list': friend_status,
|
'user_list': get_online_status(request.profile, friend_list),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Users',
|
'title': 'Admins',
|
||||||
'user_list': user_status,
|
'user_list': get_online_status(request.profile, admin_list),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Other',
|
||||||
|
'user_list': get_online_status(request.profile, all_user_status),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -162,4 +338,80 @@ def get_status_context(request):
|
||||||
def online_status_ajax(request):
|
def online_status_ajax(request):
|
||||||
return render(request, 'chat/online_status.html', {
|
return render(request, 'chat/online_status.html', {
|
||||||
'status_sections': get_status_context(request),
|
'status_sections': get_status_context(request),
|
||||||
})
|
'unread_count_lobby': get_unread_count(None, request.profile),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def get_room(user_one, user_two):
|
||||||
|
if user_one.id > user_two.id:
|
||||||
|
user_one, user_two = user_two, user_one
|
||||||
|
room, created = Room.objects.get_or_create(user_one=user_one, user_two=user_two)
|
||||||
|
return room
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def get_or_create_room(request):
|
||||||
|
decrypted_other_id = request.GET.get('other')
|
||||||
|
request_id, other_id = decrypt_url(decrypted_other_id)
|
||||||
|
|
||||||
|
if not other_id or not request_id or request_id != request.profile.id:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
other_user = Profile.objects.get(id=int(other_id))
|
||||||
|
except Exception:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
user = request.profile
|
||||||
|
|
||||||
|
if not other_user or not user:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
# TODO: each user can only create <= 300 rooms
|
||||||
|
room = get_room(other_user, user)
|
||||||
|
return JsonResponse({'room': room.id, 'other_user_id': other_user.id})
|
||||||
|
|
||||||
|
|
||||||
|
def get_unread_count(rooms, user):
|
||||||
|
if rooms:
|
||||||
|
mess = Message.objects.filter(room=OuterRef('room'),
|
||||||
|
time__gte=OuterRef('last_seen'))\
|
||||||
|
.exclude(author=user)\
|
||||||
|
.order_by().values('room')\
|
||||||
|
.annotate(unread_count=Count('pk')).values('unread_count')
|
||||||
|
|
||||||
|
return UserRoom.objects\
|
||||||
|
.filter(user=user, room__in=rooms)\
|
||||||
|
.annotate(
|
||||||
|
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
|
||||||
|
other_user=Case(
|
||||||
|
When(room__user_one=user, then='room__user_two'),
|
||||||
|
default='room__user_one',
|
||||||
|
)
|
||||||
|
).filter(unread_count__gte=1).values('other_user', 'unread_count')
|
||||||
|
else: # lobby
|
||||||
|
mess = Message.objects.filter(room__isnull=True,
|
||||||
|
time__gte=OuterRef('last_seen'))\
|
||||||
|
.exclude(author=user)\
|
||||||
|
.annotate(unread_count=Count('pk')).values('unread_count')
|
||||||
|
|
||||||
|
return UserRoom.objects\
|
||||||
|
.filter(user=user, room__isnull=True)\
|
||||||
|
.annotate(
|
||||||
|
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
|
||||||
|
).values_list('unread_count', flat=True)[0]
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def toggle_ignore(request, **kwargs):
|
||||||
|
user_id = kwargs['user_id']
|
||||||
|
if not user_id:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
try:
|
||||||
|
other_user = Profile.objects.get(id=user_id)
|
||||||
|
except:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
Ignore.toggle_ignore(request.profile, other_user)
|
||||||
|
next_url = request.GET.get('next', '/')
|
||||||
|
return HttpResponseRedirect(next_url)
|
17
dmoj/urls.py
17
dmoj/urls.py
|
@ -1,4 +1,4 @@
|
||||||
from chat_box.views import ChatView, delete_message, post_message, chat_message_ajax, online_status_ajax
|
from chat_box.views import *
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
@ -24,8 +24,8 @@ from judge.views import TitledTemplateView, about, api, blog, comment, contests,
|
||||||
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
|
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
|
||||||
problem_data_file, problem_init_view
|
problem_data_file, problem_init_view
|
||||||
from judge.views.register import ActivationView, RegistrationView
|
from judge.views.register import ActivationView, RegistrationView
|
||||||
from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, ContestSelect2View, \
|
from judge.views.select2 import AssigneeSelect2View, ChatUserSearchSelect2View, CommentSelect2View, \
|
||||||
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
|
ContestSelect2View, ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
|
||||||
UserSearchSelect2View, UserSelect2View
|
UserSearchSelect2View, UserSelect2View
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
@ -288,6 +288,7 @@ urlpatterns = [
|
||||||
|
|
||||||
url(r'^select2/', include([
|
url(r'^select2/', include([
|
||||||
url(r'^user_search$', UserSearchSelect2View.as_view(), name='user_search_select2_ajax'),
|
url(r'^user_search$', UserSearchSelect2View.as_view(), name='user_search_select2_ajax'),
|
||||||
|
url(r'^user_search_chat$', ChatUserSearchSelect2View.as_view(), name='chat_user_search_select2_ajax'),
|
||||||
url(r'^contest_users/(?P<contest>\w+)$', ContestUserSearchSelect2View.as_view(),
|
url(r'^contest_users/(?P<contest>\w+)$', ContestUserSearchSelect2View.as_view(),
|
||||||
name='contest_user_search_select2_ajax'),
|
name='contest_user_search_select2_ajax'),
|
||||||
url(r'^ticket_user$', TicketUserSelect2View.as_view(), name='ticket_user_select2_ajax'),
|
url(r'^ticket_user$', TicketUserSelect2View.as_view(), name='ticket_user_select2_ajax'),
|
||||||
|
@ -373,13 +374,15 @@ urlpatterns = [
|
||||||
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
|
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
|
||||||
|
|
||||||
url(r'^chat/', include([
|
url(r'^chat/', include([
|
||||||
url(r'^$',
|
url(r'^(?P<room_id>\d*)$', login_required(ChatView.as_view()), name='chat'),
|
||||||
login_required(ChatView.as_view()),
|
|
||||||
name='chat'),
|
|
||||||
url(r'^delete/$', delete_message, name='delete_chat_message'),
|
url(r'^delete/$', delete_message, name='delete_chat_message'),
|
||||||
url(r'^post/$', post_message, name='post_chat_message'),
|
url(r'^post/$', post_message, name='post_chat_message'),
|
||||||
url(r'^ajax$', chat_message_ajax, name='chat_message_ajax'),
|
url(r'^ajax$', chat_message_ajax, name='chat_message_ajax'),
|
||||||
url(r'^online_status/ajax$', online_status_ajax, name='online_status_ajax')
|
url(r'^online_status/ajax$', online_status_ajax, name='online_status_ajax'),
|
||||||
|
url(r'^get_or_create_room$', get_or_create_room, name='get_or_create_room'),
|
||||||
|
url(r'^update_last_seen$', update_last_seen, name='update_last_seen'),
|
||||||
|
url(r'^online_status/user/ajax$', user_online_status_ajax, name='user_online_status_ajax'),
|
||||||
|
url(r'^toggle_ignore/(?P<user_id>\d+)$', toggle_ignore, name='toggle_ignore'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
url(r'^notifications/',
|
url(r'^notifications/',
|
||||||
|
|
18
judge/migrations/0116_auto_20211011_0645.py
Normal file
18
judge/migrations/0116_auto_20211011_0645.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-10-10 23:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0115_auto_20210525_0222'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contest',
|
||||||
|
name='format_name',
|
||||||
|
field=models.CharField(choices=[('atcoder', 'AtCoder'), ('default', 'Default'), ('ecoo', 'ECOO'), ('icpc', 'ICPC'), ('ioi', 'IOI')], default='default', help_text='The contest format module to use.', max_length=32, verbose_name='contest format'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -257,5 +257,13 @@ class Friend(models.Model):
|
||||||
else:
|
else:
|
||||||
self.make_friend(current_user, new_friend)
|
self.make_friend(current_user, new_friend)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_friend_profiles(self, current_user):
|
||||||
|
try:
|
||||||
|
ret = self.objects.get(current_user=current_user).users.all()
|
||||||
|
except Friend.DoesNotExist:
|
||||||
|
ret = []
|
||||||
|
return ret
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.current_user)
|
return str(self.current_user)
|
||||||
|
|
|
@ -4,6 +4,8 @@ from django.shortcuts import get_object_or_404
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.views.generic.list import BaseListView
|
from django.views.generic.list import BaseListView
|
||||||
|
|
||||||
|
from chat_box.utils import encrypt_url
|
||||||
|
|
||||||
from judge.jinja2.gravatar import gravatar
|
from judge.jinja2.gravatar import gravatar
|
||||||
from judge.models import Comment, Contest, Organization, Problem, Profile
|
from judge.models import Comment, Contest, Organization, Problem, Profile
|
||||||
|
|
||||||
|
@ -121,3 +123,35 @@ class AssigneeSelect2View(UserSearchSelect2View):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Profile.objects.filter(assigned_tickets__isnull=False,
|
return Profile.objects.filter(assigned_tickets__isnull=False,
|
||||||
user__username__icontains=self.term).distinct()
|
user__username__icontains=self.term).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class ChatUserSearchSelect2View(BaseListView):
|
||||||
|
paginate_by = 20
|
||||||
|
|
||||||
|
def get_queryset(self): # TODO: add block
|
||||||
|
return _get_user_queryset(self.term)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.term = kwargs.get('term', request.GET.get('term', ''))
|
||||||
|
self.gravatar_size = request.GET.get('gravatar_size', 128)
|
||||||
|
self.gravatar_default = request.GET.get('gravatar_default', None)
|
||||||
|
|
||||||
|
self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank')
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'results': [
|
||||||
|
{
|
||||||
|
'text': username,
|
||||||
|
'id': encrypt_url(request.profile.id, pk),
|
||||||
|
'gravatar_url': gravatar(email, self.gravatar_size, self.gravatar_default),
|
||||||
|
'display_rank': display_rank,
|
||||||
|
} for pk, username, email, display_rank in context['object_list']],
|
||||||
|
'more': context['page_obj'].has_next(),
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
return str(obj)
|
|
@ -55,8 +55,7 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
max-height: 85%;
|
height: 75%;
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#chat-input {
|
#chat-input {
|
||||||
|
@ -90,6 +89,88 @@
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#chat-input, #chat-log .content-message {
|
||||||
|
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.info-pic {
|
||||||
|
height: 90%;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.05em;
|
||||||
|
border: 0.1px solid #ccc;
|
||||||
|
margin-left: 3em;
|
||||||
|
margin-bottom: 1.5px;
|
||||||
|
}
|
||||||
|
.info-circle {
|
||||||
|
position: absolute;
|
||||||
|
cx: 86%;
|
||||||
|
cy: 80%;
|
||||||
|
r: 6px;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
.info-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
.info-name a {
|
||||||
|
display: table-caption;
|
||||||
|
}
|
||||||
|
#chat-info {
|
||||||
|
border-bottom: 2px solid darkgray;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#refresh-button {
|
||||||
|
padding: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
height: 1.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
#refresh-button:hover {
|
||||||
|
background: lightgreen;
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
transition: 1.5s ease-in-out;
|
||||||
|
}
|
||||||
|
.status-pic {
|
||||||
|
height: 1.3em;
|
||||||
|
width: 1.3em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
.status-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.status-circle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
cx: 18px;
|
||||||
|
cy: 18px;
|
||||||
|
r: 4.5px;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
.status-row {
|
||||||
|
display: flex;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0.2em 0.2em 0.2em 1em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.status-row:hover {
|
||||||
|
background: lightgray;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.status-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.status-section-title {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
@media (max-width: 799px) {
|
@media (max-width: 799px) {
|
||||||
#chat-area {
|
#chat-area {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
|
|
|
@ -522,4 +522,14 @@ details {
|
||||||
background: $background_light_gray;
|
background: $background_light_gray;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button {
|
||||||
|
background: lightgray;
|
||||||
|
color: black !important;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button:hover {
|
||||||
|
background: gray;
|
||||||
}
|
}
|
|
@ -8,24 +8,53 @@
|
||||||
<script type="text/javascript" src="{{ static('event.js') }}"></script>
|
<script type="text/javascript" src="{{ static('event.js') }}"></script>
|
||||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||||
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
|
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
let message_template = `
|
||||||
|
{% with message=message_template %}
|
||||||
|
{% include "chat/message.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
`;
|
||||||
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.currentPage = 1;
|
window.currentPage = 1;
|
||||||
window.limit_time = 0;
|
window.limit_time = 24;
|
||||||
window.messages_per_page = 50;
|
window.messages_per_page = 50;
|
||||||
|
window.room_id = "{{room if room else ''}}";
|
||||||
|
window.unread_message = 0;
|
||||||
|
window.other_user_id = "{{other_user.id if other_user else ''}}";
|
||||||
|
window.num_pages = {{paginator.num_pages}};
|
||||||
|
window.lock = false;
|
||||||
|
|
||||||
function load_page(page) {
|
function load_page(page, refresh_html=false) {
|
||||||
$.get('?page=' + page)
|
var param = {
|
||||||
|
'page': page,
|
||||||
|
}
|
||||||
|
$.get("{{ url('chat', '') }}" + window.room_id, param)
|
||||||
.fail(function() {
|
.fail(function() {
|
||||||
console.log("Fail to load page " + page);
|
console.log("Fail to load page " + page);
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
|
if (refresh_html) {
|
||||||
|
$('#chat-log').html('');
|
||||||
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
|
window.lock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.num_pages = parseInt($('<div>' + data + '</div>').find('#num_pages').html());
|
||||||
|
var time = refresh_html ? 0 : 500;
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
let container = $('#chat-box');
|
let $chat_box = $('#chat-box');
|
||||||
let lastMsgPos = scrollTopOfBottom(container)
|
let lastMsgPos = scrollTopOfBottom($chat_box)
|
||||||
|
|
||||||
$('#loader').hide();
|
$('#loader').hide();
|
||||||
|
|
||||||
$('#chat-log').prepend(data);
|
if (refresh_html) {
|
||||||
|
$('#chat-log').append(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#chat-log').prepend(data);
|
||||||
|
}
|
||||||
|
|
||||||
$('.body-block').slice(0, window.messages_per_page).each(function() {
|
$('.body-block').slice(0, window.messages_per_page).each(function() {
|
||||||
resize_emoji($(this));
|
resize_emoji($(this));
|
||||||
|
@ -34,8 +63,14 @@
|
||||||
register_time($('.time-with-rel'));
|
register_time($('.time-with-rel'));
|
||||||
merge_authors();
|
merge_authors();
|
||||||
|
|
||||||
container.scrollTop(scrollTopOfBottom(container) - lastMsgPos);
|
if (!refresh_html) {
|
||||||
}, 500);
|
$chat_box.scrollTop(scrollTopOfBottom($chat_box) - lastMsgPos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
|
}
|
||||||
|
window.lock = false;
|
||||||
|
}, time);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +81,7 @@
|
||||||
function scrollContainer(container, loader) {
|
function scrollContainer(container, loader) {
|
||||||
container.scroll(function() {
|
container.scroll(function() {
|
||||||
if (container.scrollTop() == 0) {
|
if (container.scrollTop() == 0) {
|
||||||
if (currentPage < {{paginator.num_pages}}) {
|
if (currentPage < window.num_pages && !window.lock) {
|
||||||
currentPage++;
|
currentPage++;
|
||||||
loader.show();
|
loader.show();
|
||||||
load_page(currentPage);
|
load_page(currentPage);
|
||||||
|
@ -57,41 +92,121 @@
|
||||||
window.load_dynamic_update = function (last_msg) {
|
window.load_dynamic_update = function (last_msg) {
|
||||||
return new EventReceiver(
|
return new EventReceiver(
|
||||||
"{{ EVENT_DAEMON_LOCATION }}", "{{ EVENT_DAEMON_POLL_LOCATION }}",
|
"{{ EVENT_DAEMON_LOCATION }}", "{{ EVENT_DAEMON_POLL_LOCATION }}",
|
||||||
['chat'], last_msg, function (message) {
|
['chat_lobby', 'chat_{{request.profile.id}}'], last_msg, function (message) {
|
||||||
switch (message.type) {
|
var room = (message.type == 'lobby') ? '' : message.room;
|
||||||
case 'new_message':
|
if (message.author_id == {{request.profile.id}}) {
|
||||||
add_new_message(message.message);
|
check_new_message(message.message, message.tmp_id, room);
|
||||||
break;
|
}
|
||||||
|
else {
|
||||||
|
add_new_message(message.message, room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_new_message(message) {
|
function refresh_status() {
|
||||||
$.get({
|
$.get("{{url('online_status_ajax')}}")
|
||||||
url: "{{ url('chat_message_ajax') }}",
|
.fail(function() {
|
||||||
data: {
|
console.log("Fail to get online status");
|
||||||
message: message,
|
})
|
||||||
},
|
.done(function(data) {
|
||||||
success: function (data) {
|
if (data.status == 403) {
|
||||||
var $data = $(data);
|
console.log("Fail to retrieve data");
|
||||||
resize_emoji($data.find('.body-block'));
|
|
||||||
|
|
||||||
$('#chat-log').append($data);
|
|
||||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
|
||||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
|
||||||
register_time($('.time-with-rel'));
|
|
||||||
merge_authors();
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$('#chat-online-list').html(data).find('.toggle').each(function () {
|
||||||
|
register_toggle($(this));
|
||||||
|
});
|
||||||
|
register_click_space();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
'user': window.other_user_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
$.get("{{url('user_online_status_ajax')}}", data)
|
||||||
|
.fail(function() {
|
||||||
|
console.log("Fail to get user online status");
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
$('#chat-info').html(data);
|
||||||
|
register_time($('.time-with-rel'));
|
||||||
|
register_setting();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_message(data) {
|
||||||
|
var $data = $(data);
|
||||||
|
resize_emoji($data.find('.body-block'));
|
||||||
|
|
||||||
|
$('#chat-log').append($data);
|
||||||
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
|
register_time($('.time-with-rel'));
|
||||||
|
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
||||||
|
merge_authors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_new_message(message, room) {
|
||||||
|
function callback(update) {
|
||||||
|
if (!document['hidden']) {
|
||||||
|
if (update) update_last_seen();
|
||||||
|
refresh_status();
|
||||||
}
|
}
|
||||||
});
|
else {
|
||||||
|
window.unread_message++;
|
||||||
|
document.title = "(" + window.unread_message + ") " + "{{ _('New message(s)') }}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room == window.room_id) {
|
||||||
|
$.get({
|
||||||
|
url: "{{ url('chat_message_ajax') }}",
|
||||||
|
data: {
|
||||||
|
message: message,
|
||||||
|
},
|
||||||
|
success: function (data) {
|
||||||
|
add_message(data);
|
||||||
|
callback(true);
|
||||||
|
},
|
||||||
|
error: function (data) {
|
||||||
|
console.log('Could not add new message');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_new_message(message, tmp_id, room) {
|
||||||
|
if (room == "{{room}}") {
|
||||||
|
$.get({
|
||||||
|
url: "{{ url('chat_message_ajax') }}",
|
||||||
|
data: {
|
||||||
|
message: message,
|
||||||
|
},
|
||||||
|
success: function (data) {
|
||||||
|
var $body_block = $(data).find('.body-block');
|
||||||
|
if ($('#message-'+tmp_id).length) {
|
||||||
|
$('#message-'+tmp_id).replaceWith(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#body-block-'+tmp_id).replaceWith($body_block);
|
||||||
|
}
|
||||||
|
resize_emoji($body_block);
|
||||||
|
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
|
||||||
|
register_time($('.time-with-rel'));
|
||||||
|
remove_unread_current_user();
|
||||||
|
},
|
||||||
|
error: function (data) {
|
||||||
|
console.log('Fail to check message');
|
||||||
|
var $body = $('#body-block-'+tmp_id + ' p');
|
||||||
|
$body.css('text-decoration', 'line-through');
|
||||||
|
$body.css('text-decoration-color', 'red');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function merge_authors() {
|
function merge_authors() {
|
||||||
|
@ -119,25 +234,40 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function add_message_from_template(body, tmp_id) {
|
||||||
|
var html = message_template;
|
||||||
|
html = html.replaceAll('$body', body).replaceAll('$id', tmp_id);
|
||||||
|
var $html = $(html);
|
||||||
|
$html.find('.time-with-rel').attr('data-iso', (new Date()).toISOString());
|
||||||
|
add_message($html[0].outerHTML, false);
|
||||||
|
}
|
||||||
|
|
||||||
function submit_chat() {
|
function submit_chat() {
|
||||||
|
{% if last_msg and not request.profile.mute %}
|
||||||
if ($("#chat-input").val().trim()) {
|
if ($("#chat-input").val().trim()) {
|
||||||
var body = $('#chat-input').val().trim();
|
var body = $('#chat-input').val().trim();
|
||||||
body = body.split('\n').join('\n\n');
|
body = body.split('\n').join('\n\n');
|
||||||
|
|
||||||
var message = {
|
var message = {
|
||||||
body: body,
|
body: body,
|
||||||
|
room: window.room_id,
|
||||||
|
tmp_id: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#chat-input').val('');
|
$('#chat-input').val('');
|
||||||
|
|
||||||
|
add_message_from_template(body, message.tmp_id);
|
||||||
|
|
||||||
$.post("{{ url('post_chat_message') }}", message)
|
$.post("{{ url('post_chat_message') }}", message)
|
||||||
.fail(function(res) {
|
.fail(function(res) {
|
||||||
console.log('Fail to send message');
|
console.log('Fail to send message');
|
||||||
})
|
})
|
||||||
.done(function(res, status) {
|
.done(function(res, status) {
|
||||||
|
$('#empty_msg').hide();
|
||||||
$('#chat-input').focus();
|
$('#chat-input').focus();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
{% endif %}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resize_emoji(element) {
|
function resize_emoji(element) {
|
||||||
|
@ -165,6 +295,77 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function register_click_space() {
|
||||||
|
function callback() {
|
||||||
|
history.replaceState(null, '', "{{url('chat', '')}}" + window.room_id);
|
||||||
|
load_page(window.currentPage, true, refresh_status);
|
||||||
|
update_last_seen();
|
||||||
|
refresh_status();
|
||||||
|
$('#chat-input').focus();
|
||||||
|
}
|
||||||
|
$('.click_space').on('click', function(e) {
|
||||||
|
if ($(this).attr('id') == 'click_space_' + window.other_user_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var other_user = $(this).attr('value');
|
||||||
|
$.get("{{url('get_or_create_room')}}" + `?other=${other_user}`)
|
||||||
|
.done(function(data) {
|
||||||
|
window.currentPage = 1;
|
||||||
|
window.room_id = data.room;
|
||||||
|
window.other_user_id = data.other_user_id;
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
console.log('Fail to get_or_create_room');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
$('#lobby_row').on('click', function(e) {
|
||||||
|
if (window.room_id) {
|
||||||
|
window.currentPage = 1;
|
||||||
|
window.room_id = '';
|
||||||
|
window.other_user_id = '';
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_last_seen() {
|
||||||
|
var data = {
|
||||||
|
room: window.room_id
|
||||||
|
};
|
||||||
|
|
||||||
|
$.post("{{ url('update_last_seen') }}", data)
|
||||||
|
.fail(function(data) {
|
||||||
|
console.log('Fail to update last seen');
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_unread_current_user() {
|
||||||
|
if (window.other_user_id) {
|
||||||
|
$("#unread-count-" + window.other_user_id).hide();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#unread-count-lobby').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function register_setting() {
|
||||||
|
$('#setting-button').on('click', function() {
|
||||||
|
$('#setting-content').toggle();
|
||||||
|
});
|
||||||
|
$('#setting-content li').on('click', function() {
|
||||||
|
$(this).children('a')[0].click();
|
||||||
|
})
|
||||||
|
$('#setting-content a').on('click', function() {
|
||||||
|
var href = $(this).attr('href');
|
||||||
|
href += '?next=' + window.location.pathname;
|
||||||
|
$(this).attr('href', href);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$('#loader').hide();
|
$('#loader').hide();
|
||||||
merge_authors();
|
merge_authors();
|
||||||
|
@ -191,7 +392,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: function(data) {
|
fail: function(data) {
|
||||||
alert('Fail to delete');
|
console.log('Fail to delete');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -201,6 +402,7 @@
|
||||||
resize_emoji($(this));
|
resize_emoji($(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#chat-log").show();
|
||||||
$("#chat-log").change(function() {
|
$("#chat-log").change(function() {
|
||||||
$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
|
$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
|
||||||
});
|
});
|
||||||
|
@ -237,26 +439,9 @@
|
||||||
$('.chat-right-panel').show();
|
$('.chat-right-panel').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#refresh-button').on('click', function() {
|
$('#refresh-button').on('click', refresh_status)
|
||||||
$.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).find('.toggle').each(function () {
|
|
||||||
register_toggle($(this));
|
|
||||||
});;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
setInterval(function() {
|
setInterval(refresh_status, 2 * 60 * 1000);
|
||||||
$('#refresh-button').click();
|
|
||||||
}, 5 * 60 * 1000);
|
|
||||||
|
|
||||||
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
load_dynamic_update({{last_msg}});
|
load_dynamic_update({{last_msg}});
|
||||||
|
@ -279,11 +464,67 @@
|
||||||
$chat.focus();
|
$chat.focus();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
register_click_space();
|
||||||
|
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
if (e.keyCode === 27 && $('.tooltip').hasClass('shown')) {
|
if (e.keyCode === 27 && $('.tooltip').hasClass('shown')) {
|
||||||
toggleEmoji();
|
toggleEmoji();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$('#search-handle').replaceWith($('<select>').attr({
|
||||||
|
id: 'search-handle',
|
||||||
|
name: 'other',
|
||||||
|
onchange: 'form.submit()'
|
||||||
|
}));
|
||||||
|
var in_user_redirect = false;
|
||||||
|
$('#search-handle').select2({
|
||||||
|
placeholder: '{{ _('Search by handle...') }}',
|
||||||
|
ajax: {
|
||||||
|
url: '{{ url('chat_user_search_select2_ajax') }}'
|
||||||
|
},
|
||||||
|
minimumInputLength: 1,
|
||||||
|
escapeMarkup: function (markup) {
|
||||||
|
return markup;
|
||||||
|
},
|
||||||
|
templateResult: function (data, container) {
|
||||||
|
return $('<span>')
|
||||||
|
.append($('<img>', {
|
||||||
|
'class': 'user-search-image', src: data.gravatar_url,
|
||||||
|
width: 24, height: 24
|
||||||
|
}))
|
||||||
|
.append($('<span>', {'class': data.display_rank + ' user-search-name'}).text(data.text))
|
||||||
|
.append($('<a>', {href: '/user/' + data.text, 'class': 'user-redirect'})
|
||||||
|
.append($('<i>', {'class': 'fa fa-mail-forward'}))
|
||||||
|
.mouseover(function () {
|
||||||
|
in_user_redirect = true;
|
||||||
|
}).mouseout(function () {
|
||||||
|
in_user_redirect = false;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).on('select2:selecting', function () {
|
||||||
|
return !in_user_redirect;
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/42121565/detecting-class-change-without-setinterval
|
||||||
|
if (typeof(MutationObserver) !== undefined) {
|
||||||
|
var observer = new MutationObserver(function (event) {
|
||||||
|
if (!document['hidden'] && window.unread_message > 0) {
|
||||||
|
update_last_seen();
|
||||||
|
refresh_status();
|
||||||
|
window.unread_message = 0;
|
||||||
|
document.title = "{{_('Chat Box')}}";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class'],
|
||||||
|
childList: false,
|
||||||
|
characterData: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
register_setting();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -314,23 +555,37 @@
|
||||||
</button>
|
</button>
|
||||||
</h3>
|
</h3>
|
||||||
<div id="chat-online-content">
|
<div id="chat-online-content">
|
||||||
{% include "chat/online_status.html" %}
|
<div id="search-container">
|
||||||
|
<center>
|
||||||
|
<form id="search-form" name="form" action="{{ url('get_or_create_room') }}" method="get">
|
||||||
|
<input id="search-handle" type="text" name="search"
|
||||||
|
placeholder="{{ _('Search by handle...') }}">
|
||||||
|
</form>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
<div id="chat-online-list">
|
||||||
|
{% include "chat/online_status.html" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="chat-area" class="chat-left-panel" style="width:100%">
|
<div id="chat-area" class="chat-left-panel" style="width:100%">
|
||||||
<div id="chat-box">
|
<div id="chat-info" style="height: 8%">
|
||||||
<img src="http://opengraphicdesign.com/wp-content/uploads/2009/01/loader64.gif" id="loader">
|
{% include 'chat/user_online_status.html' %}
|
||||||
<ul id="chat-log">
|
</div>
|
||||||
{% include 'chat/message_list.html' %}
|
<div id="chat-box">
|
||||||
</ul>
|
|
||||||
</div>
|
<img src="{{static('loading.gif')}}" id="loader">
|
||||||
<div style="height: 15%">
|
<ul id="chat-log" style="display: none">
|
||||||
<a id="emoji-button" href="#" title="{{_('Emoji')}}"><i class="icofont-slightly-smile"></i></a>
|
{% include 'chat/message_list.html' %}
|
||||||
<textarea id="chat-input" placeholder="{{_('Enter your message')}}"></textarea>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip" role="tooltip">
|
<div style="height: 15%">
|
||||||
<emoji-picker></emoji-picker>
|
<a id="emoji-button" href="#" title="{{_('Emoji')}}"><i class="icofont-slightly-smile"></i></a>
|
||||||
</div>
|
<textarea id="chat-input" placeholder="{{_('Enter your message')}}"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip" role="tooltip">
|
||||||
|
<emoji-picker></emoji-picker>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
|
@ -2,53 +2,7 @@
|
||||||
#content {
|
#content {
|
||||||
margin: -1em 1em 0 0;
|
margin: -1em 1em 0 0;
|
||||||
}
|
}
|
||||||
#refresh-button {
|
|
||||||
padding: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
height: 1.5em;
|
|
||||||
width: 1.5em;
|
|
||||||
}
|
|
||||||
#refresh-button:hover {
|
|
||||||
background: lightgreen;
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
transition: 1.5s ease-in-out;
|
|
||||||
}
|
|
||||||
.status-pic {
|
|
||||||
height: 1.3em;
|
|
||||||
width: 1.3em;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
}
|
|
||||||
.status-container {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
.status-circle {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
cx: 18px;
|
|
||||||
cy: 18px;
|
|
||||||
r: 4.5px;
|
|
||||||
stroke: white;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.status-row {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.status-list {
|
|
||||||
padding: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
.status-section-title {
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
@ -143,14 +97,55 @@
|
||||||
.body-block:hover {
|
.body-block:hover {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
#chat-input, #chat-log .content-message {
|
.active-span {
|
||||||
font-family: "Apple Color Emoji", "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
margin-top: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
color: #636363;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unread-count {
|
||||||
|
float: right;
|
||||||
|
color: white;
|
||||||
|
background-color: darkcyan;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
#search-form {
|
||||||
|
float: inherit;
|
||||||
|
}
|
||||||
|
#search-container {
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
#setting {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#setting-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
#setting-content li {
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#setting-content li:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-container {
|
||||||
|
position:fixed;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media (min-width: 800px) {
|
||||||
#page-container {
|
|
||||||
position:fixed;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@media (max-width: 799px) {
|
@media (max-width: 799px) {
|
||||||
html, body {
|
html, body {
|
||||||
|
@ -160,5 +155,11 @@
|
||||||
#mobile ul {
|
#mobile ul {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.info-pic {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.active-span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="user-time">
|
<div class="user-time">
|
||||||
<span class="username {{ message.author.css_class }}">
|
<span class="username {{ message.author.css_class }}">
|
||||||
<a href="{{ url('user_page', message.author.user.username) }}">
|
<a href="{{ url('user_page', message.author.user.username) }}">
|
||||||
{{message.author}}
|
{{ message.author }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="time">
|
<span class="time">
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="content-message">
|
<span class="content-message">
|
||||||
<div class="body-block" title="{{ message.time|date('g:i a') }}">
|
<div class="body-block" id="body-block-{{ message.id }}" title="{{ message.time|date('g:i a') }}">
|
||||||
{% if request.user.is_staff %}
|
{% if request.user.is_staff %}
|
||||||
<a class="chatbtn_remove_mess" value="{{message.id}}" style="color:red; cursor: pointer;">
|
<a class="chatbtn_remove_mess" value="{{message.id}}" style="color:red; cursor: pointer;">
|
||||||
{{_('Delete')}}
|
{{_('Delete')}}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
{% if object_list %}
|
||||||
|
<div style="display: none" id="num_pages">{{num_pages}}</div>
|
||||||
{% for message in object_list | reverse%}
|
{% for message in object_list | reverse%}
|
||||||
{% include "chat/message.html" %}
|
{% include "chat/message.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<center id="empty_msg">{{_('You are connect now. Say something to start the conversation.')}}</center>
|
||||||
|
{% endif %}
|
||||||
{% if REQUIRE_JAX %}
|
{% if REQUIRE_JAX %}
|
||||||
{% include "mathjax-load.html" %}
|
{% include "mathjax-load.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "comments/math.html" %}
|
{% include "comments/math.html" %}
|
|
@ -1,3 +1,14 @@
|
||||||
|
<li class="status-row" id="lobby_row">
|
||||||
|
<div class="status-container">
|
||||||
|
<img src="{{ static('icons/logo.png') }}" style="height:1.3em">
|
||||||
|
</div>
|
||||||
|
<span style="padding-left:0.5em">
|
||||||
|
<b>{{_('Lobby')}}</b>
|
||||||
|
</span>
|
||||||
|
<span class="spacer">
|
||||||
|
<span class="unread-count" id="unread-count-lobby">{{unread_count_lobby if unread_count_lobby}}</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
{% for section in status_sections %}
|
{% for section in status_sections %}
|
||||||
{% if section.user_list %}
|
{% if section.user_list %}
|
||||||
<div class="status-section-title toggle open">
|
<div class="status-section-title toggle open">
|
||||||
|
@ -8,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<ul class="status-list toggled">
|
<ul class="status-list toggled">
|
||||||
{% for user in section.user_list %}
|
{% for user in section.user_list %}
|
||||||
<li style="padding-left: 0.1em" class="status-row">
|
<li class="status-row">
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<img src="{{ gravatar(user.user, 135) }}" class="status-pic">
|
<img src="{{ gravatar(user.user, 135) }}" class="status-pic">
|
||||||
<svg style="position:absolute;" height="32" width="32">
|
<svg style="position:absolute;" height="32" width="32">
|
||||||
|
@ -19,6 +30,9 @@
|
||||||
<span style="padding-left:0.3em" class="username {{ user.user.css_class }}">
|
<span style="padding-left:0.3em" class="username {{ user.user.css_class }}">
|
||||||
{{ link_user(user.user) }}
|
{{ link_user(user.user) }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="click_space spacer" value="{{user.url}}" id="click_space_{{user.user.id}}">
|
||||||
|
<span class="unread-count" id="unread-count-{{user.user.id}}">{{user.unread_count if user.unread_count}}</span>
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
35
templates/chat/user_online_status.html
Normal file
35
templates/chat/user_online_status.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% if other_user %}
|
||||||
|
<div class="status-container" style="height: 100%">
|
||||||
|
<img src="{{ gravatar(other_user.user, 135) }}" class="info-pic">
|
||||||
|
<svg style="position:absolute; height:100%; width: 110%">
|
||||||
|
<circle class="info-circle"
|
||||||
|
fill="{{'green' if other_online else 'red'}}"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<span class="info-name username">
|
||||||
|
{% if other_user %}
|
||||||
|
<a href="{{url('user_page', other_user)}}">{{other_user.user.username}}</a>
|
||||||
|
{% else%}
|
||||||
|
<a href="#" style="margin-left: 3em">{{ _('Lobby') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
{% if other_user and not other_online %}
|
||||||
|
<span class="active-span">{{ relative_time(other_user.last_access, abs=_('Last online on {time}'), rel=_('Online {time}'), format=_('g:i a d/m/Y')) }}</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if other_user %}
|
||||||
|
<span style="margin-right: 0.3em" id="setting">
|
||||||
|
<button class="control-button" style="height:100%;" id="setting-button">
|
||||||
|
<i class="fa fa-ellipsis-h"></i>
|
||||||
|
</button>
|
||||||
|
<div id="setting-content">
|
||||||
|
<li>
|
||||||
|
<a href="{{url('toggle_ignore', other_user.id)}}" style="color: {{'green' if is_ignored else 'red'}}">{{_('Unignore') if is_ignored else _('Ignore')}}</a>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="active-span">{{online_count}} {{_('users are online')}}</span>
|
||||||
|
{% endif %}
|
|
@ -2,15 +2,6 @@
|
||||||
|
|
||||||
{% block media %}
|
{% block media %}
|
||||||
<style>
|
<style>
|
||||||
#control-button {
|
|
||||||
margin-left: 0.8em;
|
|
||||||
background: lightgray;
|
|
||||||
color: black !important;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
#control-button:hover {
|
|
||||||
background: gray;
|
|
||||||
}
|
|
||||||
{% if request.user.is_authenticated and is_member %}
|
{% if request.user.is_authenticated and is_member %}
|
||||||
#control-panel {
|
#control-panel {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -63,7 +54,7 @@
|
||||||
$('.blog-content').hide();
|
$('.blog-content').hide();
|
||||||
$('.blog-sidebar').show();
|
$('.blog-sidebar').show();
|
||||||
});
|
});
|
||||||
$('#control-button').click(function(e) {
|
$('.control-button').click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#control-panel').toggle("fast");
|
$('#control-panel').toggle("fast");
|
||||||
})
|
})
|
||||||
|
@ -92,7 +83,7 @@
|
||||||
class="unselectable button">{{ _('Request membership') }}</a>
|
class="unselectable button">{{ _('Request membership') }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="control-button"><i class="fa fa-ellipsis-h"></i></button>
|
<button class="control-button"><i class="fa fa-ellipsis-h"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue