mirror of
https://github.com/rudy3333/eversync.git
synced 2025-07-01 08:36:02 +00:00
Compare commits
2 commits
7b1ac19a4e
...
e85ed60e20
Author | SHA1 | Date | |
---|---|---|---|
e85ed60e20 | |||
97ef207ba9 |
5 changed files with 164 additions and 15 deletions
|
@ -127,6 +127,28 @@ textarea#message-content {
|
|||
.dark #ai-assist-button i {
|
||||
color: #e4e6eb;
|
||||
}
|
||||
|
||||
.reaction-bubble {
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
background: #eee;
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
transition: background 0.2s, border 0.2s;
|
||||
}
|
||||
.reaction-bubble.my-reaction {
|
||||
background: #ffd966;
|
||||
border: 1px solid #ffc107;
|
||||
}
|
||||
.add-reaction-row {
|
||||
display: none;
|
||||
}
|
||||
.message-container:hover .add-reaction-row {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -171,7 +193,7 @@ textarea#message-content {
|
|||
|
||||
<div id="chat-log" style="flex: 1; overflow-y: auto; border: 1px solid #ccc; padding: 10px; margin-bottom: 15px;">
|
||||
{% for msg in messages %}
|
||||
<div style="margin: 10px 0; display: flex; align-items: start; {% if msg.sender == request.user %}flex-direction: row-reverse;{% endif %} gap: 8px;">
|
||||
<div class="message-container" style="margin: 10px 0; display: flex; align-items: start; {% if msg.sender == request.user %}flex-direction: row-reverse;{% endif %} gap: 8px;">
|
||||
<div style="width: 32px; height: 32px; flex-shrink: 0;">
|
||||
{% if msg.sender.profile.profile_picture %}
|
||||
<img src="{{ msg.sender.profile.profile_picture.url }}" alt="Profile Picture" style="width: 32px; height: 32px; border-radius: 4px; object-fit: cover; border: 1px solid #ccc;">
|
||||
|
@ -203,6 +225,22 @@ textarea#message-content {
|
|||
<button type="submit" class="btn btn-danger btn-sm" style="margin-top:5px; background-color: #dc3545; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer;">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if msg.reactions.all %}
|
||||
{% with last_reaction=msg.reactions.all.last %}
|
||||
<div class="reactions-row" style="margin: 4px 0; display: flex; gap: 4px;">
|
||||
<span class="reaction-bubble{% if last_reaction.user == request.user %} my-reaction{% endif %}" data-message-id="{{ msg.id }}" data-reaction="{{ last_reaction.reaction_type }}">
|
||||
{{ last_reaction.reaction_type }}
|
||||
</span>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<div class="add-reaction-row" style="margin: 2px 0; gap: 4px;">
|
||||
<button type="button" class="add-reaction-btn" data-message-id="{{ msg.id }}" data-emoji="👍" style="background: none; border: none; cursor: pointer; font-size: 18px;">👍</button>
|
||||
<button type="button" class="add-reaction-btn" data-message-id="{{ msg.id }}" data-emoji="❤️" style="background: none; border: none; cursor: pointer; font-size: 18px;">❤️</button>
|
||||
<button type="button" class="add-reaction-btn" data-message-id="{{ msg.id }}" data-emoji="😂" style="background: none; border: none; cursor: pointer; font-size: 18px;">😂</button>
|
||||
<button type="button" class="add-reaction-btn" data-message-id="{{ msg.id }}" data-emoji="😮" style="background: none; border: none; cursor: pointer; font-size: 18px;">😮</button>
|
||||
<button type="button" class="add-reaction-btn" data-message-id="{{ msg.id }}" data-emoji="😢" style="background: none; border: none; cursor: pointer; font-size: 18px;">😢</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -449,5 +487,30 @@ const toggle = document.getElementById("themeToggle");
|
|||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).on('click', '.add-reaction-btn', function() {
|
||||
var messageId = $(this).data('message-id');
|
||||
var emoji = $(this).data('emoji');
|
||||
var csrfToken = $('input[name="csrfmiddlewaretoken"]').val();
|
||||
$.post(`/message/${messageId}/add_reaction/`, {
|
||||
reaction_type: emoji,
|
||||
csrfmiddlewaretoken: csrfToken
|
||||
}, function(response) {
|
||||
location.reload(); // For now, reload to show updated reactions
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.reaction-bubble', function() {
|
||||
var messageId = $(this).data('message-id');
|
||||
var emoji = $(this).data('reaction');
|
||||
var csrfToken = $('input[name="csrfmiddlewaretoken"]').val();
|
||||
$.post(`/message/${messageId}/remove_reaction/`, {
|
||||
reaction_type: emoji,
|
||||
csrfmiddlewaretoken: csrfToken
|
||||
}, function(response) {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
29
eversyncc/migrations/0033_messagereaction.py
Normal file
29
eversyncc/migrations/0033_messagereaction.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 5.2.1 on 2025-06-24 21:05
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('eversyncc', '0032_embed_order'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MessageReaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reaction_type', models.CharField(max_length=16)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reactions', to='eversyncc.message')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('message', 'user', 'reaction_type')},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -150,4 +150,16 @@ def create_user_profile(sender, instance, created, **kwargs):
|
|||
|
||||
@receiver(post_save, sender=User)
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
instance.profile.save()
|
||||
instance.profile.save()
|
||||
|
||||
class MessageReaction(models.Model):
|
||||
message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='reactions')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
reaction_type = models.CharField(max_length=16)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('message', 'user', 'reaction_type')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} reacted {self.reaction_type} to message {self.message.id}"
|
|
@ -78,6 +78,8 @@ urlpatterns = [
|
|||
path('web_archive/view/<int:archive_id>', views.view_web_archive, name='view_web_archive'),
|
||||
path('update_profile_picture/', views.update_profile_picture, name='update_profile_picture'),
|
||||
path('delete_profile_picture/', views.delete_profile_picture, name='delete_profile_picture'),
|
||||
path('message/<int:message_id>/add_reaction/', views.add_reaction, name='add_reaction'),
|
||||
path('message/<int:message_id>/remove_reaction/', views.remove_reaction, name='remove_reaction'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.contrib.auth import logout
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import PasswordChangeView
|
||||
from .forms import UsernameChangeForm, DocumentForm, EventForm, NoteForm, TaskForm
|
||||
from .models import Document, Event, Notes, Embed, Task, RichDocument, Message, WebArchive
|
||||
from .models import Document, Event, Notes, Embed, Task, RichDocument, Message, WebArchive, MessageReaction
|
||||
from django.contrib import messages
|
||||
from allauth.account.views import LoginView as AllauthLoginView
|
||||
import os
|
||||
|
@ -52,6 +52,20 @@ import selenium
|
|||
import pyclamd
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Constants
|
||||
FORBIDDEN_EXTENSIONS = ['.html', '.htm', '.php', '.exe', '.js', '.sh', '.bat']
|
||||
SELENIUM_CHROME_ARGS = [
|
||||
'--headless=new',
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-gpu',
|
||||
'--lang=en-US',
|
||||
]
|
||||
SELENIUM_CHROME_PREFS = {
|
||||
'intl.accept_languages': 'en,en_US',
|
||||
'profile.default_content_setting_values.geolocation': 2,
|
||||
}
|
||||
|
||||
def scan_file_with_clamav(file_path):
|
||||
try:
|
||||
cd = pyclamd.ClamdNetworkSocket(host='127.0.0.1', port=3310)
|
||||
|
@ -176,8 +190,7 @@ def upload_file(request):
|
|||
file_size = document.file.size
|
||||
|
||||
|
||||
forbidden_extensions=['.html','.htm','.php','.exe','.js','.sh','.bat']
|
||||
if ext in forbidden_extensions:
|
||||
if ext in FORBIDDEN_EXTENSIONS:
|
||||
request.session.flush()
|
||||
logout(request)
|
||||
return HttpResponse("<html><body><script>alert('Uploading possibly malicious files is forbidden. You have been logged out.'); location.reload();</script></body></html>")
|
||||
|
@ -902,7 +915,7 @@ def chat_with_user(request, username):
|
|||
messages = Message.objects.filter(
|
||||
Q(sender=request.user, receiver=other_user) |
|
||||
Q(sender=other_user, receiver=request.user)
|
||||
).select_related('sender', 'receiver').order_by('timestamp')
|
||||
).select_related('sender', 'receiver').prefetch_related('reactions__user').order_by('timestamp')
|
||||
pinned_message = Message.objects.filter(pinned=True, sender=request.user).order_by('-timestamp').first()
|
||||
Message.objects.filter(sender=other_user, receiver=request.user, seen=False).update(seen=True, seen_at=now())
|
||||
|
||||
|
@ -1212,15 +1225,9 @@ def save_web_archive(request):
|
|||
try:
|
||||
# Set up Chrome options
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument('--headless=new')
|
||||
chrome_options.add_argument('--no-sandbox')
|
||||
chrome_options.add_argument('--disable-dev-shm-usage')
|
||||
chrome_options.add_argument('--disable-gpu')
|
||||
chrome_options.add_argument("--lang=en-US")
|
||||
chrome_options.add_experimental_option("prefs", {
|
||||
"intl.accept_languages": "en,en_US",
|
||||
"profile.default_content_setting_values.geolocation": 2,
|
||||
})
|
||||
for arg in SELENIUM_CHROME_ARGS:
|
||||
chrome_options.add_argument(arg)
|
||||
chrome_options.add_experimental_option("prefs", SELENIUM_CHROME_PREFS)
|
||||
# Initialize Chrome driver
|
||||
driver = webdriver.Chrome(options=chrome_options)
|
||||
url = form.cleaned_data['url']
|
||||
|
@ -1371,3 +1378,39 @@ def reorder_embeds(request):
|
|||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
|
||||
@email_verified_required
|
||||
@login_required
|
||||
def add_reaction(request, message_id):
|
||||
if request.method == 'POST':
|
||||
reaction_type = request.POST.get('reaction_type')
|
||||
if not reaction_type:
|
||||
return JsonResponse({'error': 'No reaction_type provided'}, status=400)
|
||||
message = get_object_or_404(Message, id=message_id)
|
||||
reaction, created = MessageReaction.objects.get_or_create(
|
||||
message=message,
|
||||
user=request.user,
|
||||
reaction_type=reaction_type
|
||||
)
|
||||
return JsonResponse({'success': True, 'created': created})
|
||||
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
||||
|
||||
@email_verified_required
|
||||
@login_required
|
||||
def remove_reaction(request, message_id):
|
||||
if request.method == 'POST':
|
||||
reaction_type = request.POST.get('reaction_type')
|
||||
if not reaction_type:
|
||||
return JsonResponse({'error': 'No reaction_type provided'}, status=400)
|
||||
message = get_object_or_404(Message, id=message_id)
|
||||
try:
|
||||
reaction = MessageReaction.objects.get(
|
||||
message=message,
|
||||
user=request.user,
|
||||
reaction_type=reaction_type
|
||||
)
|
||||
reaction.delete()
|
||||
return JsonResponse({'success': True})
|
||||
except MessageReaction.DoesNotExist:
|
||||
return JsonResponse({'error': 'Reaction not found'}, status=404)
|
||||
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue