devnamdev2003 commited on
Commit ·
727a40a
1
Parent(s): 52f3c7c
up3
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +53 -0
- .vscode/launch.json +16 -0
- Dockerfile +25 -0
- LICENSE +21 -0
- README.md +1 -0
- chat/__init__.py +0 -0
- chat/admin.py +44 -0
- chat/apps.py +5 -0
- chat/consumers.py +71 -0
- chat/models.py +32 -0
- chat/routing.py +6 -0
- chat/serializers.py +14 -0
- chat/static/chat/css/base.css +127 -0
- chat/static/chat/css/chat.css +271 -0
- chat/static/chat/css/color.css +65 -0
- chat/static/chat/css/home.css +122 -0
- chat/static/chat/css/login.css +169 -0
- chat/static/chat/css/profile.css +87 -0
- chat/static/chat/css/search.css +89 -0
- chat/static/chat/css/settings.css +136 -0
- chat/static/chat/css/signup.css +141 -0
- chat/static/chat/favicon_io/favicon.ico +0 -0
- chat/static/chat/favicon_io/site.webmanifest +49 -0
- chat/static/chat/js/base.js +39 -0
- chat/static/chat/js/chat.js +60 -0
- chat/static/chat/js/home.js +27 -0
- chat/static/chat/js/login.js +74 -0
- chat/static/chat/js/profile.js +1 -0
- chat/static/chat/js/search.js +1 -0
- chat/static/chat/js/settings.js +1 -0
- chat/static/chat/js/signup.js +91 -0
- chat/tests.py +3 -0
- chat/views/__init__.py +6 -0
- chat/views/chat_view.py +68 -0
- chat/views/friends_view.py +136 -0
- chat/views/home_view.py +110 -0
- chat/views/password_view.py +14 -0
- chat/views/test_view.py +65 -0
- chat/views/user_view.py +128 -0
- devnoms/__init__.py +0 -0
- devnoms/asgi.py +16 -0
- devnoms/settings.py +161 -0
- devnoms/urls.py +61 -0
- devnoms/wsgi.py +16 -0
- manage.py +22 -0
- requirements.txt +0 -0
- static/admin/css/autocomplete.css +279 -0
- static/admin/css/base.css +1179 -0
- static/admin/css/changelists.css +343 -0
- static/admin/css/dark_mode.css +130 -0
.gitignore
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore virtual environment
|
| 2 |
+
venv/
|
| 3 |
+
venv/*
|
| 4 |
+
|
| 5 |
+
env/
|
| 6 |
+
env/*
|
| 7 |
+
|
| 8 |
+
.env
|
| 9 |
+
|
| 10 |
+
# Python bytecode
|
| 11 |
+
*.py[cod]
|
| 12 |
+
__pycache__/
|
| 13 |
+
|
| 14 |
+
# Django stuff
|
| 15 |
+
*.log
|
| 16 |
+
*.pot
|
| 17 |
+
*.pyc
|
| 18 |
+
*.pyo
|
| 19 |
+
*.pyd
|
| 20 |
+
*.sqlite3
|
| 21 |
+
db.sqlite3
|
| 22 |
+
|
| 23 |
+
# Migrations
|
| 24 |
+
*/migrations/*.py
|
| 25 |
+
*/migrations/*.pyc
|
| 26 |
+
|
| 27 |
+
# Environment variables
|
| 28 |
+
# .env
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Static and media files (if not managed by version control)
|
| 32 |
+
# staticfiles/
|
| 33 |
+
# media/
|
| 34 |
+
|
| 35 |
+
# Ignore coverage and testing files
|
| 36 |
+
htmlcov/
|
| 37 |
+
.coverage
|
| 38 |
+
.tox/
|
| 39 |
+
nosetests.xml
|
| 40 |
+
coverage.xml
|
| 41 |
+
*.cover
|
| 42 |
+
*.log
|
| 43 |
+
*.pid
|
| 44 |
+
*.seed
|
| 45 |
+
*.manifest
|
| 46 |
+
*.lock
|
| 47 |
+
|
| 48 |
+
# Ignore IDE/editor settings
|
| 49 |
+
# .vscode/
|
| 50 |
+
.idea/
|
| 51 |
+
*.swp
|
| 52 |
+
*.swo
|
| 53 |
+
*.swn
|
.vscode/launch.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "0.2.0",
|
| 3 |
+
"configurations": [
|
| 4 |
+
{
|
| 5 |
+
"name": "Run Daphne Server",
|
| 6 |
+
"type": "debugpy",
|
| 7 |
+
"request": "launch",
|
| 8 |
+
"program": "${workspaceFolder}/venv/Scripts/daphne.exe", // Adjusted path for Windows
|
| 9 |
+
"args": [
|
| 10 |
+
"devnoms.asgi:application"
|
| 11 |
+
],
|
| 12 |
+
"console": "integratedTerminal"
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|
| 16 |
+
|
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13.5-slim
|
| 2 |
+
|
| 3 |
+
# Create non-root user (HF requirement)
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
|
| 6 |
+
# Set work directory
|
| 7 |
+
WORKDIR /home/user/app
|
| 8 |
+
|
| 9 |
+
# Copy requirements first (better cache)
|
| 10 |
+
COPY requirements.txt .
|
| 11 |
+
|
| 12 |
+
# Install dependencies
|
| 13 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
+
|
| 15 |
+
# Copy project files
|
| 16 |
+
COPY . .
|
| 17 |
+
|
| 18 |
+
# Switch to non-root user
|
| 19 |
+
USER user
|
| 20 |
+
|
| 21 |
+
# Expose HF port
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
|
| 24 |
+
# Run Django with Gunicorn (PRODUCTION SAFE)
|
| 25 |
+
CMD ["daphne", "devnoms.asgi:application", "--bind", "0.0.0.0","--port" , "7860"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Dev Namdev
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -4,6 +4,7 @@ emoji: 🏢
|
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: red
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
|
|
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: red
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
chat/__init__.py
ADDED
|
File without changes
|
chat/admin.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import UserRelation
|
| 3 |
+
from django.contrib import admin
|
| 4 |
+
from django.contrib.auth.admin import UserAdmin
|
| 5 |
+
from django.contrib.auth.models import User
|
| 6 |
+
from .models import Messages
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class MessagesAdmin(admin.ModelAdmin):
|
| 10 |
+
list_display = ("sender_name", "receiver_name", "id" , "seen", "description")
|
| 11 |
+
list_filter = ("sender_name", "receiver_name", "seen")
|
| 12 |
+
search_fields = ("sender_name__username", "receiver_name__username", "description")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
admin.site.register(Messages, MessagesAdmin)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# Define a custom admin class for the User model
|
| 19 |
+
class CustomUserAdmin(UserAdmin):
|
| 20 |
+
list_display = (
|
| 21 |
+
"id",
|
| 22 |
+
"username",
|
| 23 |
+
"email",
|
| 24 |
+
"first_name",
|
| 25 |
+
"last_name",
|
| 26 |
+
"is_staff",
|
| 27 |
+
"date_joined",
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Unregister the default UserAdmin
|
| 32 |
+
admin.site.unregister(User)
|
| 33 |
+
|
| 34 |
+
# Register the User model with the custom admin class
|
| 35 |
+
admin.site.register(User, CustomUserAdmin)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class UserRelationAdmin(admin.ModelAdmin):
|
| 39 |
+
list_display = ("id", "user", "friend", "accepted", "relation_key")
|
| 40 |
+
list_filter = ("user", "accepted")
|
| 41 |
+
search_fields = ("user__username", "friend")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
admin.site.register(UserRelation, UserRelationAdmin)
|
chat/apps.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class chatConfig(AppConfig):
|
| 5 |
+
name = 'chat'
|
chat/consumers.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from channels.generic.websocket import AsyncWebsocketConsumer
|
| 3 |
+
|
| 4 |
+
from django.utils import timezone
|
| 5 |
+
from channels.db import database_sync_to_async
|
| 6 |
+
|
| 7 |
+
# Define an async function to save the message to the database
|
| 8 |
+
@database_sync_to_async
|
| 9 |
+
def save_message(sender_name, receiver_name, message):
|
| 10 |
+
from django.contrib.auth.models import User
|
| 11 |
+
from chat.models import Messages
|
| 12 |
+
# Get User instances for sender and receiver
|
| 13 |
+
sender = User.objects.get(username=sender_name)
|
| 14 |
+
receiver = User.objects.get(username=receiver_name)
|
| 15 |
+
|
| 16 |
+
# Create a new message
|
| 17 |
+
new_message = Messages(
|
| 18 |
+
description=message,
|
| 19 |
+
sender_name=sender,
|
| 20 |
+
receiver_name=receiver,
|
| 21 |
+
time=timezone.now().time(), # Store the current time
|
| 22 |
+
timestamp=timezone.now(), # Store the timestamp
|
| 23 |
+
seen=True
|
| 24 |
+
)
|
| 25 |
+
new_message.save()
|
| 26 |
+
class ChatConsumer(AsyncWebsocketConsumer):
|
| 27 |
+
async def connect(self):
|
| 28 |
+
self.room_name = self.scope['url_route']['kwargs']['room_name']
|
| 29 |
+
self.room_group_name = f'chat_{self.room_name}'
|
| 30 |
+
# print(self.room_name, self.room_group_name)
|
| 31 |
+
# Join room group
|
| 32 |
+
await self.channel_layer.group_add(
|
| 33 |
+
self.room_group_name,
|
| 34 |
+
self.channel_name
|
| 35 |
+
)
|
| 36 |
+
await self.accept()
|
| 37 |
+
|
| 38 |
+
async def disconnect(self, close_code):
|
| 39 |
+
# Leave room group
|
| 40 |
+
await self.channel_layer.group_discard(
|
| 41 |
+
self.room_group_name,
|
| 42 |
+
self.channel_name
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
# Receive message from WebSocket
|
| 46 |
+
async def receive(self, text_data):
|
| 47 |
+
text_data_json = json.loads(text_data)
|
| 48 |
+
message = text_data_json['message']
|
| 49 |
+
sender_name = text_data_json['sender_name']
|
| 50 |
+
receiver_name = text_data_json['receiver_name']
|
| 51 |
+
await save_message(sender_name, receiver_name, message)
|
| 52 |
+
# Send message to room group
|
| 53 |
+
await self.channel_layer.group_send(
|
| 54 |
+
self.room_group_name,
|
| 55 |
+
{
|
| 56 |
+
'type': 'chat_message',
|
| 57 |
+
'message': message,
|
| 58 |
+
'sender': sender_name
|
| 59 |
+
}
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Receive message from room group
|
| 63 |
+
async def chat_message(self, event):
|
| 64 |
+
message = event['message']
|
| 65 |
+
sender = event['sender']
|
| 66 |
+
|
| 67 |
+
# Send message to WebSocket
|
| 68 |
+
await self.send(text_data=json.dumps({
|
| 69 |
+
'message': message,
|
| 70 |
+
'sender': sender
|
| 71 |
+
}))
|
chat/models.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
from django.utils import timezone
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class UserRelation(models.Model):
|
| 7 |
+
user = models.ForeignKey(
|
| 8 |
+
User, on_delete=models.CASCADE, related_name="user_relations"
|
| 9 |
+
)
|
| 10 |
+
friend = models.ForeignKey(
|
| 11 |
+
User, on_delete=models.CASCADE, related_name="friend_relations", default=None
|
| 12 |
+
)
|
| 13 |
+
accepted = models.BooleanField(default=False)
|
| 14 |
+
relation_key = models.CharField(max_length=255, blank=True, null=True) # Add relation_key field
|
| 15 |
+
def __str__(self):
|
| 16 |
+
return f"{self.user.username} - {self.friend.username}"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Messages(models.Model):
|
| 20 |
+
description = models.TextField()
|
| 21 |
+
sender_name = models.ForeignKey(
|
| 22 |
+
User, on_delete=models.CASCADE, related_name="sender"
|
| 23 |
+
)
|
| 24 |
+
receiver_name = models.ForeignKey(
|
| 25 |
+
User, on_delete=models.CASCADE, related_name="receiver"
|
| 26 |
+
)
|
| 27 |
+
time = models.TimeField(auto_now_add=True)
|
| 28 |
+
seen = models.BooleanField(default=False)
|
| 29 |
+
timestamp = models.DateTimeField(default=timezone.now, blank=True)
|
| 30 |
+
|
| 31 |
+
class Meta:
|
| 32 |
+
ordering = ("timestamp",)
|
chat/routing.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import consumers
|
| 3 |
+
|
| 4 |
+
websocket_urlpatterns = [
|
| 5 |
+
path('ws/chat/<str:room_name>/', consumers.ChatConsumer.as_asgi()),
|
| 6 |
+
]
|
chat/serializers.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rest_framework import serializers
|
| 2 |
+
from .models import Messages
|
| 3 |
+
from django.contrib.auth.models import User
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MessageSerializer(serializers.ModelSerializer):
|
| 8 |
+
|
| 9 |
+
sender_name = serializers.SlugRelatedField(many=False, slug_field='username', queryset=User.objects.all())
|
| 10 |
+
receiver_name = serializers.SlugRelatedField(many=False, slug_field='username', queryset=User.objects.all())
|
| 11 |
+
|
| 12 |
+
class Meta:
|
| 13 |
+
model = Messages
|
| 14 |
+
fields = ['sender_name', 'receiver_name', 'description', 'time']
|
chat/static/chat/css/base.css
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
::-webkit-scrollbar {
|
| 2 |
+
width: 5px;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
::-webkit-scrollbar-thumb {
|
| 6 |
+
background-color: var(--scrollbar-thumb);
|
| 7 |
+
border-radius: 50px;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
::-webkit-scrollbar-track {
|
| 11 |
+
background-color: var(--scrollbar-track);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
* {
|
| 15 |
+
margin: 0;
|
| 16 |
+
padding: 0;
|
| 17 |
+
box-sizing: border-box;
|
| 18 |
+
-webkit-tap-highlight-color: transparent;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
html,
|
| 22 |
+
body {
|
| 23 |
+
font-family: 'Roboto', sans-serif;
|
| 24 |
+
background: var(--bg-gradient);
|
| 25 |
+
color: var(--text-color);
|
| 26 |
+
min-height: 100%;
|
| 27 |
+
animation: gradientMove var(--gradient-speed) infinite;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.main_box {
|
| 31 |
+
display: flex;
|
| 32 |
+
justify-content: center;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.header {
|
| 36 |
+
display: flex;
|
| 37 |
+
justify-content: space-between;
|
| 38 |
+
align-items: center;
|
| 39 |
+
width: 100%;
|
| 40 |
+
max-width: 450px;
|
| 41 |
+
background-color: var(--menu-bg);
|
| 42 |
+
padding: 15px;
|
| 43 |
+
box-shadow: 0 2px 8px var(--shadow-color);
|
| 44 |
+
position: relative;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.header h1 {
|
| 48 |
+
color: var(--text-color);
|
| 49 |
+
font-size: 18px;
|
| 50 |
+
text-align: center;
|
| 51 |
+
flex: 1;
|
| 52 |
+
margin: 0;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.header button {
|
| 56 |
+
background: transparent;
|
| 57 |
+
border: none;
|
| 58 |
+
color: var(--text-color);
|
| 59 |
+
cursor: pointer;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.header button img {
|
| 63 |
+
width: 24px;
|
| 64 |
+
height: 24px;
|
| 65 |
+
filter: var(--icon-color);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.menu {
|
| 69 |
+
position: relative;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.menu button {
|
| 73 |
+
background: transparent;
|
| 74 |
+
border: none;
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.dropdown-content {
|
| 79 |
+
display: none;
|
| 80 |
+
position: absolute;
|
| 81 |
+
right: 0;
|
| 82 |
+
top: 40px;
|
| 83 |
+
background-color: var(--menu-bg);
|
| 84 |
+
min-width: 160px;
|
| 85 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 86 |
+
z-index: 1;
|
| 87 |
+
border-radius: 5px;
|
| 88 |
+
opacity: 0;
|
| 89 |
+
transition: opacity 0.3s ease;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.dropdown-content a {
|
| 93 |
+
color: var(--text-color);
|
| 94 |
+
padding: 10px 15px;
|
| 95 |
+
text-decoration: none;
|
| 96 |
+
border-radius: 5px;
|
| 97 |
+
display: block;
|
| 98 |
+
font-size: 16px;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.dropdown-content a:hover {
|
| 102 |
+
background-color: var(--highlight-color);
|
| 103 |
+
border-radius: 5px;
|
| 104 |
+
color: var(--bg-color);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.dropdown-content.show {
|
| 108 |
+
display: block;
|
| 109 |
+
border-radius: 5px;
|
| 110 |
+
opacity: 1;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.btn {
|
| 114 |
+
background: var(--btn-bg);
|
| 115 |
+
color: var(--btn-text-color);
|
| 116 |
+
padding: 6px 15px;
|
| 117 |
+
border: none;
|
| 118 |
+
border-radius: 20px;
|
| 119 |
+
cursor: pointer;
|
| 120 |
+
font-size: 14px;
|
| 121 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.btn:hover {
|
| 125 |
+
background: var(--btn-bg-hover);
|
| 126 |
+
transform: scale(1.02);
|
| 127 |
+
}
|
chat/static/chat/css/chat.css
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--theme-color: #8f91ea;
|
| 3 |
+
--timestamp-color: #b5b5b5;
|
| 4 |
+
--status-online: #28a745;
|
| 5 |
+
--status-offline: #888888;
|
| 6 |
+
--bg-gradient: linear-gradient(135deg, #121212, #1c1c1c);
|
| 7 |
+
--bg-color: #121212;
|
| 8 |
+
--text-color: #f5f5f5;
|
| 9 |
+
--menu-bg: #1c1c1c;
|
| 10 |
+
--menu-text-color: #8f91ea;
|
| 11 |
+
--shadow-color: rgba(0, 0, 0, 0.5);
|
| 12 |
+
--icon-color: invert(100%);
|
| 13 |
+
--highlight-color: #8f91ea;
|
| 14 |
+
--friend-item-hover: #2b2b2b;
|
| 15 |
+
--btn-text-color: #ffffff;
|
| 16 |
+
--btn-bg: linear-gradient(135deg, #6a75c9, #5a67c8);
|
| 17 |
+
--btn-bg-hover: linear-gradient(135deg, #5a67c8, #4854c8);
|
| 18 |
+
--btn-bg-danger: linear-gradient(135deg, #e57373, #c0392b);
|
| 19 |
+
--btn-bg-danger-hover: linear-gradient(135deg, #c0392b, #96281b);
|
| 20 |
+
--scrollbar-thumb: #444444;
|
| 21 |
+
--scrollbar-track: #1c1c1c;
|
| 22 |
+
--bg-slider-color: rgba(143, 145, 234, 0.1);
|
| 23 |
+
--switch-slider: #555555;
|
| 24 |
+
--switch-slider-before: #ffffff;
|
| 25 |
+
--switch-slider-checked: #8f91ea;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
[data-theme='light'] {
|
| 29 |
+
--theme-color: #8f91ea;
|
| 30 |
+
--timestamp-color: #6c757d;
|
| 31 |
+
--status-online: #28a745;
|
| 32 |
+
--status-offline: #adb5bd;
|
| 33 |
+
--bg-gradient: linear-gradient(135deg, #ffffff, #f4f4f4);
|
| 34 |
+
--bg-color: #ffffff;
|
| 35 |
+
--text-color: #212529;
|
| 36 |
+
--menu-bg: #e9ecef;
|
| 37 |
+
--menu-text-color: #8f91ea;
|
| 38 |
+
--shadow-color: rgba(0, 0, 0, 0.1);
|
| 39 |
+
--icon-color: none;
|
| 40 |
+
--highlight-color: #8f91ea;
|
| 41 |
+
--friend-item-hover: #f1f3f5;
|
| 42 |
+
--btn-text-color: #ffffff;
|
| 43 |
+
--btn-bg: linear-gradient(135deg, #6a75c9, #5a67c8);
|
| 44 |
+
--btn-bg-hover: linear-gradient(135deg, #5a67c8, #4854c8);
|
| 45 |
+
--btn-bg-danger: linear-gradient(135deg, #e57373, #c0392b);
|
| 46 |
+
--btn-bg-danger-hover: linear-gradient(135deg, #c0392b, #96281b);
|
| 47 |
+
--scrollbar-thumb: #cccccc;
|
| 48 |
+
--scrollbar-track: #f7f7f7;
|
| 49 |
+
--bg-slider-color: rgba(143, 145, 234, 0.1);
|
| 50 |
+
--switch-slider: #cccccc;
|
| 51 |
+
--switch-slider-before: #ffffff;
|
| 52 |
+
--switch-slider-checked: #8f91ea;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
::-webkit-scrollbar {
|
| 56 |
+
width: 5px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
::-webkit-scrollbar-thumb {
|
| 60 |
+
background-color: var(--scrollbar-thumb);
|
| 61 |
+
border-radius: 50px;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
::-webkit-scrollbar-track {
|
| 65 |
+
background-color: var(--scrollbar-track);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
* {
|
| 69 |
+
margin: 0;
|
| 70 |
+
padding: 0;
|
| 71 |
+
box-sizing: border-box;
|
| 72 |
+
-webkit-tap-highlight-color: transparent;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
html,
|
| 76 |
+
body {
|
| 77 |
+
font-family: 'Roboto', sans-serif;
|
| 78 |
+
background: var(--bg-gradient);
|
| 79 |
+
color: var(--text-color);
|
| 80 |
+
height: 100%;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.main_box {
|
| 84 |
+
display: flex;
|
| 85 |
+
justify-content: center;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.header {
|
| 89 |
+
display: flex;
|
| 90 |
+
justify-content: space-between;
|
| 91 |
+
align-items: center;
|
| 92 |
+
width: 100%;
|
| 93 |
+
max-width: 450px;
|
| 94 |
+
background-color: var(--menu-bg);
|
| 95 |
+
padding: 15px;
|
| 96 |
+
box-shadow: 0 2px 8px var(--shadow-color);
|
| 97 |
+
position: relative;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.header h1 {
|
| 101 |
+
color: var(--text-color);
|
| 102 |
+
font-size: 18px;
|
| 103 |
+
text-align: center;
|
| 104 |
+
flex: 1;
|
| 105 |
+
margin: 0;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.header button {
|
| 109 |
+
background: transparent;
|
| 110 |
+
border: none;
|
| 111 |
+
color: var(--text-color);
|
| 112 |
+
cursor: pointer;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.header button img {
|
| 116 |
+
width: 24px;
|
| 117 |
+
height: 24px;
|
| 118 |
+
filter: var(--icon-color);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.menu {
|
| 122 |
+
position: relative;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.menu button {
|
| 126 |
+
background: transparent;
|
| 127 |
+
border: none;
|
| 128 |
+
cursor: pointer;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.dropdown-content {
|
| 132 |
+
display: none;
|
| 133 |
+
position: absolute;
|
| 134 |
+
right: 0;
|
| 135 |
+
top: 40px;
|
| 136 |
+
background-color: var(--menu-bg);
|
| 137 |
+
min-width: 160px;
|
| 138 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 139 |
+
z-index: 1;
|
| 140 |
+
border-radius: 5px;
|
| 141 |
+
opacity: 0;
|
| 142 |
+
transition: opacity 0.3s ease;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.dropdown-content a {
|
| 146 |
+
color: var(--text-color);
|
| 147 |
+
padding: 10px 15px;
|
| 148 |
+
text-decoration: none;
|
| 149 |
+
border-radius: 5px;
|
| 150 |
+
display: block;
|
| 151 |
+
font-size: 16px;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.dropdown-content a:hover {
|
| 155 |
+
background-color: var(--highlight-color);
|
| 156 |
+
border-radius: 5px;
|
| 157 |
+
color: var(--bg-color);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.dropdown-content.show {
|
| 161 |
+
display: block;
|
| 162 |
+
border-radius: 5px;
|
| 163 |
+
opacity: 1;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.btn {
|
| 167 |
+
background: var(--btn-bg);
|
| 168 |
+
color: var(--btn-text-color);
|
| 169 |
+
padding: 6px 15px;
|
| 170 |
+
border: none;
|
| 171 |
+
border-radius: 20px;
|
| 172 |
+
cursor: pointer;
|
| 173 |
+
font-size: 14px;
|
| 174 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.btn:hover {
|
| 178 |
+
background: var(--btn-bg-hover);
|
| 179 |
+
transform: scale(1.02);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.chat_box {
|
| 183 |
+
background: var(--bg-color);
|
| 184 |
+
width: 100%;
|
| 185 |
+
max-width: 450px;
|
| 186 |
+
max-height: 900px;
|
| 187 |
+
display: flex;
|
| 188 |
+
height: calc(var(--vh, 1vh) * 100);
|
| 189 |
+
flex-direction: column;
|
| 190 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.chat-area {
|
| 194 |
+
flex: 1;
|
| 195 |
+
padding: 10px;
|
| 196 |
+
overflow-y: auto;
|
| 197 |
+
display: flex;
|
| 198 |
+
flex-direction: column;
|
| 199 |
+
gap: 10px;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
.chat-message {
|
| 203 |
+
display: flex;
|
| 204 |
+
flex-direction: column;
|
| 205 |
+
max-width: 70%;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.chat-message.sent {
|
| 209 |
+
align-self: flex-end;
|
| 210 |
+
text-align: right;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.chat-message.received {
|
| 214 |
+
align-self: flex-start;
|
| 215 |
+
text-align: left;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.message-text {
|
| 219 |
+
background-color: var(--menu-bg);
|
| 220 |
+
color: var(--text-color);
|
| 221 |
+
padding: 10px 15px;
|
| 222 |
+
border-radius: 10px;
|
| 223 |
+
word-wrap: break-word;
|
| 224 |
+
line-height: 1.5;
|
| 225 |
+
text-align: left;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.timestamp {
|
| 229 |
+
font-size: 12px;
|
| 230 |
+
color: var(--timestamp-color);
|
| 231 |
+
margin-top: 4px;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.input-area {
|
| 235 |
+
display: flex;
|
| 236 |
+
align-items: center;
|
| 237 |
+
border-radius: 30px;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.message-input {
|
| 241 |
+
flex: 1;
|
| 242 |
+
background-color: var(--menu-bg);
|
| 243 |
+
color: var(--text-color);
|
| 244 |
+
border: 8px solid var(--menu-bg);
|
| 245 |
+
resize: none;
|
| 246 |
+
outline: none;
|
| 247 |
+
border-radius: 20px;
|
| 248 |
+
height: 46px;
|
| 249 |
+
max-height: 110px;
|
| 250 |
+
overflow-y: auto;
|
| 251 |
+
line-height: 20px;
|
| 252 |
+
padding: 5px 15px;
|
| 253 |
+
margin: 10px 0px 10px 10px;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
.send-button {
|
| 257 |
+
margin: 10px 10px 10px 5px;
|
| 258 |
+
padding: 5px 12px 6px 12px;
|
| 259 |
+
border: none;
|
| 260 |
+
border-radius: 50%;
|
| 261 |
+
background: var(--btn-bg);
|
| 262 |
+
color: var(--btn-text-color);
|
| 263 |
+
cursor: pointer;
|
| 264 |
+
font-size: 25px;
|
| 265 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.send-button:hover {
|
| 269 |
+
background: var(--btn-bg-hover);
|
| 270 |
+
transform: scale(1.02);
|
| 271 |
+
}
|
chat/static/chat/css/color.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--theme-color: #8f91ea;
|
| 3 |
+
--timestamp-color: #b5b5b5;
|
| 4 |
+
--status-online: #28a745;
|
| 5 |
+
--status-offline: #888888;
|
| 6 |
+
--bg-gradient: linear-gradient(135deg, #121212, #1c1c1c);
|
| 7 |
+
--bg-color: #121212;
|
| 8 |
+
--text-color: #f5f5f5;
|
| 9 |
+
--menu-bg: #1c1c1c;
|
| 10 |
+
--menu-text-color: #8f91ea;
|
| 11 |
+
--shadow-color: rgba(0, 0, 0, 0.5);
|
| 12 |
+
--icon-color: invert(100%);
|
| 13 |
+
--highlight-color: #8f91ea;
|
| 14 |
+
--friend-item-hover: #2b2b2b;
|
| 15 |
+
--btn-text-color: #ffffff;
|
| 16 |
+
--btn-bg: linear-gradient(135deg, #6a75c9, #5a67c8);
|
| 17 |
+
--btn-bg-hover: linear-gradient(135deg, #5a67c8, #4854c8);
|
| 18 |
+
--btn-bg-danger: linear-gradient(135deg, #e57373, #c0392b);
|
| 19 |
+
--btn-bg-danger-hover: linear-gradient(135deg, #c0392b, #96281b);
|
| 20 |
+
--scrollbar-thumb: #444444;
|
| 21 |
+
--scrollbar-track: #1c1c1c;
|
| 22 |
+
--bg-slider-color: rgba(143, 145, 234, 0.1);
|
| 23 |
+
--switch-slider: #555555;
|
| 24 |
+
--switch-slider-before: #ffffff;
|
| 25 |
+
--switch-slider-checked: #8f91ea;
|
| 26 |
+
--error-message: red;
|
| 27 |
+
--spinner-border: #f3f3f3;
|
| 28 |
+
--gradient-speed: 5s;
|
| 29 |
+
--input-border: #6a75c9;
|
| 30 |
+
--input-focus-border: #8f91ea;
|
| 31 |
+
--bg-gradient: linear-gradient(45deg, #1c1e3a, #2a2d4b, #3c3f5b);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
[data-theme="light"] {
|
| 35 |
+
--theme-color: #8f91ea;
|
| 36 |
+
--timestamp-color: #6c757d;
|
| 37 |
+
--status-online: #28a745;
|
| 38 |
+
--status-offline: #adb5bd;
|
| 39 |
+
--bg-gradient: linear-gradient(135deg, #ffffff, #f4f4f4);
|
| 40 |
+
--bg-color: #ffffff;
|
| 41 |
+
--text-color: #212529;
|
| 42 |
+
--menu-bg: #e9ecef;
|
| 43 |
+
--menu-text-color: #8f91ea;
|
| 44 |
+
--shadow-color: rgba(0, 0, 0, 0.1);
|
| 45 |
+
--icon-color: none;
|
| 46 |
+
--highlight-color: #8f91ea;
|
| 47 |
+
--friend-item-hover: #f1f3f5;
|
| 48 |
+
--btn-text-color: #ffffff;
|
| 49 |
+
--btn-bg: linear-gradient(135deg, #6a75c9, #5a67c8);
|
| 50 |
+
--btn-bg-hover: linear-gradient(135deg, #5a67c8, #4854c8);
|
| 51 |
+
--btn-bg-danger: linear-gradient(135deg, #e57373, #c0392b);
|
| 52 |
+
--btn-bg-danger-hover: linear-gradient(135deg, #c0392b, #96281b);
|
| 53 |
+
--scrollbar-thumb: #cccccc;
|
| 54 |
+
--scrollbar-track: #f7f7f7;
|
| 55 |
+
--bg-slider-color: rgba(143, 145, 234, 0.1);
|
| 56 |
+
--switch-slider: #cccccc;
|
| 57 |
+
--switch-slider-before: #ffffff;
|
| 58 |
+
--switch-slider-checked: #8f91ea;
|
| 59 |
+
--error-message: red;
|
| 60 |
+
--spinner-border: #f3f3f3;
|
| 61 |
+
--gradient-speed: 5s;
|
| 62 |
+
--input-border: #6a75c9;
|
| 63 |
+
--input-focus-border: #8f91ea;
|
| 64 |
+
--bg-gradient: linear-gradient(45deg, #1c1e3a, #2a2d4b, #3c3f5b);
|
| 65 |
+
}
|
chat/static/chat/css/home.css
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.home_box {
|
| 2 |
+
background: var(--bg-color);
|
| 3 |
+
border-radius: 20px;
|
| 4 |
+
width: 100%;
|
| 5 |
+
max-width: 450px;
|
| 6 |
+
max-height: 900px;
|
| 7 |
+
display: flex;
|
| 8 |
+
height: calc(var(--vh, 1vh) * 100);
|
| 9 |
+
flex-direction: column;
|
| 10 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.friends-list {
|
| 14 |
+
flex: 1;
|
| 15 |
+
background-color: var(--bg-color);
|
| 16 |
+
margin-top: 1px;
|
| 17 |
+
overflow-y: auto;
|
| 18 |
+
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.friend-item {
|
| 22 |
+
display: flex;
|
| 23 |
+
align-items: center;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
padding: 14px;
|
| 26 |
+
border-bottom: 1px solid var(--shadow-color);
|
| 27 |
+
cursor: pointer;
|
| 28 |
+
transition: background-color 0.3s ease;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.friend-item:hover {
|
| 32 |
+
background-color: var(--friend-item-hover);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.friend-item .left {
|
| 36 |
+
display: flex;
|
| 37 |
+
align-items: center;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.friend-item img {
|
| 41 |
+
width: 45px;
|
| 42 |
+
height: 45px;
|
| 43 |
+
border-radius: 50%;
|
| 44 |
+
margin-right: 15px;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.friend-item .details {
|
| 48 |
+
display: flex;
|
| 49 |
+
flex-direction: column;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.friend-item .details p {
|
| 53 |
+
margin: 2px 0;
|
| 54 |
+
color: var(--text-color);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.friend-item .details .name {
|
| 58 |
+
font-weight: bold;
|
| 59 |
+
font-size: 16px;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.friend-item .details .last-message {
|
| 63 |
+
font-size: 14px;
|
| 64 |
+
color: var(--menu-text-color);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.friend-item .right {
|
| 68 |
+
display: flex;
|
| 69 |
+
align-items: center;
|
| 70 |
+
justify-content: center;
|
| 71 |
+
margin-left: auto;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.status {
|
| 75 |
+
width: 12px;
|
| 76 |
+
height: 12px;
|
| 77 |
+
border-radius: 50%;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.status.online {
|
| 81 |
+
background-color: var(--status-online);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.status.offline {
|
| 85 |
+
background-color: var(--status-offline);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.bottom-nav {
|
| 89 |
+
display: flex;
|
| 90 |
+
justify-content: space-around;
|
| 91 |
+
align-items: center;
|
| 92 |
+
background-color: var(--menu-bg);
|
| 93 |
+
padding: 15px;
|
| 94 |
+
box-shadow: 0 -4px 8px var(--shadow-color);
|
| 95 |
+
position: relative;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.bottom-nav .slider {
|
| 99 |
+
position: absolute;
|
| 100 |
+
width: 60px;
|
| 101 |
+
height: 60px;
|
| 102 |
+
background-color: var(--bg-slider-color);
|
| 103 |
+
border-radius: 50%;
|
| 104 |
+
top: 50%;
|
| 105 |
+
transform: translateY(-50%);
|
| 106 |
+
z-index: 0;
|
| 107 |
+
transition: all 0.3s ease;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.bottom-nav button {
|
| 111 |
+
background: transparent;
|
| 112 |
+
border: none;
|
| 113 |
+
cursor: pointer;
|
| 114 |
+
z-index: 1;
|
| 115 |
+
position: relative;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.bottom-nav img {
|
| 119 |
+
width: 30px;
|
| 120 |
+
height: 30px;
|
| 121 |
+
filter: var(--icon-color);
|
| 122 |
+
}
|
chat/static/chat/css/login.css
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.main_box {
|
| 2 |
+
display: flex;
|
| 3 |
+
justify-content: center;
|
| 4 |
+
align-items: center;
|
| 5 |
+
height: 100vh;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.login-box {
|
| 9 |
+
background: var(--menu-bg);
|
| 10 |
+
width: 95%;
|
| 11 |
+
max-width: 450px;
|
| 12 |
+
display: flex;
|
| 13 |
+
flex-direction: column;
|
| 14 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 15 |
+
padding: 20px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.logo img {
|
| 19 |
+
width: 100px;
|
| 20 |
+
margin: 20px;
|
| 21 |
+
display: block;
|
| 22 |
+
margin-left: auto;
|
| 23 |
+
margin-right: auto;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.login-box h2 {
|
| 27 |
+
color: var(--theme-color);
|
| 28 |
+
font-size: 24px;
|
| 29 |
+
margin-bottom: 20px;
|
| 30 |
+
text-align: center;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.form-group {
|
| 34 |
+
margin-bottom: 20px;
|
| 35 |
+
text-align: left;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.form-group label {
|
| 39 |
+
font-size: 14px;
|
| 40 |
+
color: var(--text-color);
|
| 41 |
+
margin-bottom: 8px;
|
| 42 |
+
display: block;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.input-group {
|
| 46 |
+
position: relative;
|
| 47 |
+
width: 100%;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.input-group input {
|
| 51 |
+
width: 100%;
|
| 52 |
+
padding: 12px;
|
| 53 |
+
font-size: 16px;
|
| 54 |
+
border: 2px solid var(--input-border);
|
| 55 |
+
border-radius: 6px;
|
| 56 |
+
background: var(--bg-color);
|
| 57 |
+
color: var(--text-color);
|
| 58 |
+
transition: border 0.3s;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.input-group input:focus {
|
| 62 |
+
outline: none;
|
| 63 |
+
border-color: var(--input-focus-border);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.eye-icon {
|
| 67 |
+
position: absolute;
|
| 68 |
+
top: 50%;
|
| 69 |
+
right: 10px;
|
| 70 |
+
transform: translateY(-50%);
|
| 71 |
+
cursor: pointer;
|
| 72 |
+
z-index: 1;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.eye-icon img {
|
| 76 |
+
width: 20px;
|
| 77 |
+
height: 20px;
|
| 78 |
+
filter: var(--icon-color);
|
| 79 |
+
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.login-btn button {
|
| 83 |
+
background: var(--btn-bg);
|
| 84 |
+
color: var(--btn-text-color);
|
| 85 |
+
padding: 16px;
|
| 86 |
+
border: none;
|
| 87 |
+
border-radius: 6px;
|
| 88 |
+
cursor: pointer;
|
| 89 |
+
font-size: 16px;
|
| 90 |
+
width: 100%;
|
| 91 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.login-btn button:hover {
|
| 95 |
+
background: var(--btn-bg-hover);
|
| 96 |
+
transform: scale(1.01);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.error-message {
|
| 100 |
+
color: var(--error-message);
|
| 101 |
+
font-size: 14px;
|
| 102 |
+
text-align: center;
|
| 103 |
+
margin-top: 10px;
|
| 104 |
+
text-align: left;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.forgot-password {
|
| 108 |
+
font-size: 14px;
|
| 109 |
+
text-align: center;
|
| 110 |
+
margin: 20px;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.forgot-password a {
|
| 114 |
+
color: var(--highlight-color);
|
| 115 |
+
text-decoration: none;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.forgot-password a:hover {
|
| 119 |
+
text-decoration: underline;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.register-link {
|
| 123 |
+
margin: 20px;
|
| 124 |
+
font-size: 14px;
|
| 125 |
+
text-align: center;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.register-link a {
|
| 129 |
+
color: var(--highlight-color);
|
| 130 |
+
text-decoration: none;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.register-link a:hover {
|
| 134 |
+
text-decoration: underline;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
.spinner {
|
| 138 |
+
border: 3px solid var(--spinner-border);
|
| 139 |
+
border-top: 3px solid var(--theme-color);
|
| 140 |
+
border-radius: 50%;
|
| 141 |
+
width: 14px;
|
| 142 |
+
height: 14px;
|
| 143 |
+
animation: spin 1s linear infinite;
|
| 144 |
+
display: none;
|
| 145 |
+
margin-left: 5px;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
@keyframes spin {
|
| 149 |
+
0% {
|
| 150 |
+
transform: rotate(0deg);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
100% {
|
| 154 |
+
transform: rotate(360deg);
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.remember-me {
|
| 159 |
+
font-size: 14px;
|
| 160 |
+
color: var(--text-color);
|
| 161 |
+
margin-bottom: 20px;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.remember-me label {
|
| 165 |
+
font-size: 14px;
|
| 166 |
+
color: var(--text-color);
|
| 167 |
+
margin-bottom: 8px;
|
| 168 |
+
display: inline-block;
|
| 169 |
+
}
|
chat/static/chat/css/profile.css
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.profile_box {
|
| 2 |
+
background: var(--bg-color);
|
| 3 |
+
width: 100%;
|
| 4 |
+
max-width: 450px;
|
| 5 |
+
max-height: 900px;
|
| 6 |
+
display: flex;
|
| 7 |
+
height: calc(var(--vh, 1vh) * 100);
|
| 8 |
+
flex-direction: column;
|
| 9 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
.profile-container {
|
| 13 |
+
width: 100%;
|
| 14 |
+
padding: 50px 20px;
|
| 15 |
+
text-align: center;
|
| 16 |
+
height: 100%;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.profile-image {
|
| 20 |
+
width: 100px;
|
| 21 |
+
height: 100px;
|
| 22 |
+
border-radius: 50%;
|
| 23 |
+
margin: 0 auto;
|
| 24 |
+
border: 4px solid var(--highlight-color);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.username {
|
| 28 |
+
font-size: 18px;
|
| 29 |
+
font-weight: bold;
|
| 30 |
+
margin-top: 10px;
|
| 31 |
+
color: var(--highlight-color);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.user-status {
|
| 35 |
+
font-size: 14px;
|
| 36 |
+
color: var(--text-color);
|
| 37 |
+
margin-top: 5px;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.status-online {
|
| 41 |
+
color: var(--status-online)
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.status-offline {
|
| 45 |
+
color: var(--status-offline);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.bio {
|
| 49 |
+
margin-top: 15px;
|
| 50 |
+
font-size: 16px;
|
| 51 |
+
color: var(--text-color);
|
| 52 |
+
text-align: center;
|
| 53 |
+
font-style: italic;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.profile-stats {
|
| 57 |
+
display: flex;
|
| 58 |
+
justify-content: space-around;
|
| 59 |
+
margin-top: 15px;
|
| 60 |
+
font-size: 16px;
|
| 61 |
+
color: var(--text-color);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.profile-stats div {
|
| 65 |
+
text-align: center;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.profile-stats div strong {
|
| 69 |
+
font-size: 18px;
|
| 70 |
+
color: var(--highlight-color);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.user-details {
|
| 74 |
+
margin-top: 20px;
|
| 75 |
+
text-align: left;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.user-details p {
|
| 79 |
+
margin: 10px 0;
|
| 80 |
+
font-size: 16px;
|
| 81 |
+
color: var(--text-color);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.user-details span {
|
| 85 |
+
font-weight: bold;
|
| 86 |
+
color: var(--highlight-color);
|
| 87 |
+
}
|
chat/static/chat/css/search.css
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.search_box {
|
| 2 |
+
background: var(--bg-color);
|
| 3 |
+
border-radius: 20px;
|
| 4 |
+
width: 100%;
|
| 5 |
+
max-width: 450px;
|
| 6 |
+
max-height: 900px;
|
| 7 |
+
display: flex;
|
| 8 |
+
height: calc(var(--vh, 1vh) * 100);
|
| 9 |
+
flex-direction: column;
|
| 10 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
.search-bar {
|
| 16 |
+
display: flex;
|
| 17 |
+
align-items: center;
|
| 18 |
+
margin: 15px;
|
| 19 |
+
background: var(--friend-item-hover);
|
| 20 |
+
padding: 10px;
|
| 21 |
+
border-radius: 10px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.search-bar input {
|
| 25 |
+
flex: 1;
|
| 26 |
+
padding: 8px 10px;
|
| 27 |
+
border: none;
|
| 28 |
+
background: transparent;
|
| 29 |
+
color: var(--text-color);
|
| 30 |
+
font-size: 16px;
|
| 31 |
+
outline: none;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.search-bar button {
|
| 35 |
+
background: transparent;
|
| 36 |
+
border: none;
|
| 37 |
+
cursor: pointer;
|
| 38 |
+
display: flex;
|
| 39 |
+
align-items: center;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.search-bar img {
|
| 43 |
+
width: 24px;
|
| 44 |
+
height: 24px;
|
| 45 |
+
filter: var(--icon-color);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.results-list {
|
| 49 |
+
flex: 1;
|
| 50 |
+
background-color: var(--bg-color);
|
| 51 |
+
margin-top: 1px;
|
| 52 |
+
overflow-y: auto;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.result-item {
|
| 56 |
+
display: flex;
|
| 57 |
+
align-items: center;
|
| 58 |
+
justify-content: space-between;
|
| 59 |
+
padding: 14px;
|
| 60 |
+
border-bottom: 1px solid var(--shadow-color);
|
| 61 |
+
cursor: pointer;
|
| 62 |
+
transition: background-color 0.3s ease;
|
| 63 |
+
transition: transform 0.3s ease;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.result-item:hover {
|
| 67 |
+
background-color: var(--friend-item-hover);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.result-item img {
|
| 71 |
+
width: 45px;
|
| 72 |
+
height: 45px;
|
| 73 |
+
border-radius: 50%;
|
| 74 |
+
margin-right: 15px;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.result-item .details {
|
| 78 |
+
flex: 1;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.result-item .details p {
|
| 82 |
+
margin: 2px 0;
|
| 83 |
+
color: var(--text-color);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.result-item .details .name {
|
| 87 |
+
font-weight: bold;
|
| 88 |
+
font-size: 16px;
|
| 89 |
+
}
|
chat/static/chat/css/settings.css
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.settings_box {
|
| 2 |
+
background: var(--bg-color);
|
| 3 |
+
border-radius: 20px;
|
| 4 |
+
width: 100%;
|
| 5 |
+
max-width: 450px;
|
| 6 |
+
max-height: 900px;
|
| 7 |
+
display: flex;
|
| 8 |
+
height: calc(var(--vh, 1vh) * 100);
|
| 9 |
+
flex-direction: column;
|
| 10 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.settings-options {
|
| 14 |
+
flex: 1;
|
| 15 |
+
background-color: var(--bg-color);
|
| 16 |
+
margin-top: 1px;
|
| 17 |
+
overflow-y: auto;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.section-title {
|
| 21 |
+
padding: 15px 15px 15px 14px;
|
| 22 |
+
font-size: 24px;
|
| 23 |
+
color: var(--text-color);
|
| 24 |
+
font-weight: bold;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.settings-option {
|
| 28 |
+
display: flex;
|
| 29 |
+
align-items: center;
|
| 30 |
+
justify-content: space-between;
|
| 31 |
+
padding: 14px;
|
| 32 |
+
border-bottom: 1px solid var(--shadow-color);
|
| 33 |
+
cursor: pointer;
|
| 34 |
+
transition: background-color 0.3s ease;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.settings-option:hover {
|
| 38 |
+
background-color: var(--menu-bg);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.settings-option label {
|
| 42 |
+
color: var(--text-color);
|
| 43 |
+
font-size: 16px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.settings-option input {
|
| 47 |
+
margin-right: 10px;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.setting-options .switch {
|
| 51 |
+
position: relative;
|
| 52 |
+
display: inline-block;
|
| 53 |
+
width: 47px;
|
| 54 |
+
height: 28px;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.setting-options .switch input {
|
| 58 |
+
opacity: 0;
|
| 59 |
+
width: 0;
|
| 60 |
+
height: 0;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.setting-options .slider {
|
| 64 |
+
position: absolute;
|
| 65 |
+
cursor: pointer;
|
| 66 |
+
top: 0;
|
| 67 |
+
left: 0;
|
| 68 |
+
right: 0;
|
| 69 |
+
bottom: 0;
|
| 70 |
+
background-color: var(--switch-slider);
|
| 71 |
+
transition: 0.4s;
|
| 72 |
+
border-radius: 34px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.setting-options .slider:before {
|
| 76 |
+
position: absolute;
|
| 77 |
+
content: "";
|
| 78 |
+
height: 20px;
|
| 79 |
+
width: 20px;
|
| 80 |
+
border-radius: 50%;
|
| 81 |
+
left: 4px;
|
| 82 |
+
bottom: 4px;
|
| 83 |
+
background-color: var(--switch-slider-before);
|
| 84 |
+
transition: 0.4s;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.setting-options input:checked+.slider {
|
| 88 |
+
background-color: var(--switch-slider-checked);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.setting-options input:checked+.slider:before {
|
| 92 |
+
background-color: var(--switch-slider-before);
|
| 93 |
+
transform: translateX(20px);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.settings-option select {
|
| 97 |
+
background-color: var(--bg-color);
|
| 98 |
+
color: var(--text-color);
|
| 99 |
+
border: 1px solid var(--highlight-color);
|
| 100 |
+
padding: 10px;
|
| 101 |
+
font-size: 16px;
|
| 102 |
+
border-radius: 5px;
|
| 103 |
+
outline: none;
|
| 104 |
+
transition: background-color 0.3s ease, color 0.3s ease;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.settings-option select:focus {
|
| 108 |
+
background-color: var(--menu-bg);
|
| 109 |
+
color: var(--highlight-color);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.settings-option option {
|
| 113 |
+
background-color: var(--bg-color);
|
| 114 |
+
color: var(--text-color);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.settings-option select:hover {
|
| 118 |
+
cursor: pointer;
|
| 119 |
+
border-color: var(--highlight-color);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.btn {
|
| 123 |
+
background: var(--btn-bg-danger);
|
| 124 |
+
color: var(--btn-text-color);
|
| 125 |
+
padding: 6px 15px;
|
| 126 |
+
border: none;
|
| 127 |
+
border-radius: 20px;
|
| 128 |
+
cursor: pointer;
|
| 129 |
+
font-size: 14px;
|
| 130 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.btn:hover {
|
| 134 |
+
background: var(--btn-bg-danger-hover);
|
| 135 |
+
transform: scale(1.02);
|
| 136 |
+
}
|
chat/static/chat/css/signup.css
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.main_box {
|
| 2 |
+
display: flex;
|
| 3 |
+
justify-content: center;
|
| 4 |
+
align-items: center;
|
| 5 |
+
height: 100vh;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.signup-box {
|
| 9 |
+
background: var(--menu-bg);
|
| 10 |
+
width: 99%;
|
| 11 |
+
max-width: 450px;
|
| 12 |
+
display: flex;
|
| 13 |
+
flex-direction: column;
|
| 14 |
+
box-shadow: 0 8px 16px var(--shadow-color);
|
| 15 |
+
padding: 20px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.logo img {
|
| 19 |
+
width: 100px;
|
| 20 |
+
margin: 20px;
|
| 21 |
+
display: block;
|
| 22 |
+
margin-left: auto;
|
| 23 |
+
margin-right: auto;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.signup-box h2 {
|
| 27 |
+
color: var(--theme-color);
|
| 28 |
+
font-size: 24px;
|
| 29 |
+
margin-bottom: 20px;
|
| 30 |
+
text-align: center;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.form-group {
|
| 34 |
+
margin-bottom: 20px;
|
| 35 |
+
text-align: left;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.form-group label {
|
| 39 |
+
font-size: 14px;
|
| 40 |
+
color: var(--text-color);
|
| 41 |
+
margin-bottom: 8px;
|
| 42 |
+
display: block;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.input-group {
|
| 46 |
+
position: relative;
|
| 47 |
+
width: 100%;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.input-group input {
|
| 51 |
+
width: 100%;
|
| 52 |
+
padding: 12px;
|
| 53 |
+
font-size: 16px;
|
| 54 |
+
border: 2px solid var(--input-border);
|
| 55 |
+
border-radius: 6px;
|
| 56 |
+
background: var(--bg-color);
|
| 57 |
+
color: var(--text-color);
|
| 58 |
+
transition: border 0.3s;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.input-group input:focus {
|
| 62 |
+
outline: none;
|
| 63 |
+
border-color: var(--input-focus-border);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.eye-icon {
|
| 67 |
+
position: absolute;
|
| 68 |
+
top: 50%;
|
| 69 |
+
right: 10px;
|
| 70 |
+
transform: translateY(-50%);
|
| 71 |
+
cursor: pointer;
|
| 72 |
+
z-index: 1;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.eye-icon img {
|
| 76 |
+
width: 20px;
|
| 77 |
+
height: 20px;
|
| 78 |
+
filter: var(--icon-color);
|
| 79 |
+
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.signup-btn button {
|
| 83 |
+
background: var(--btn-bg);
|
| 84 |
+
color: var(--btn-text-color);
|
| 85 |
+
padding: 16px;
|
| 86 |
+
border: none;
|
| 87 |
+
border-radius: 6px;
|
| 88 |
+
cursor: pointer;
|
| 89 |
+
font-size: 16px;
|
| 90 |
+
width: 100%;
|
| 91 |
+
transition: background 0.3s ease, transform 0.2s ease;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.signup-btn button:hover {
|
| 95 |
+
background: var(--btn-bg-hover);
|
| 96 |
+
transform: scale(1.01);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.error-message {
|
| 100 |
+
color: var(--error-message);
|
| 101 |
+
font-size: 14px;
|
| 102 |
+
text-align: center;
|
| 103 |
+
margin-top: 10px;
|
| 104 |
+
text-align: left;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.login-link {
|
| 108 |
+
margin: 20px;
|
| 109 |
+
font-size: 14px;
|
| 110 |
+
text-align: center;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.login-link a {
|
| 114 |
+
color: var(--highlight-color);
|
| 115 |
+
text-decoration: none;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.login-link a:hover {
|
| 119 |
+
text-decoration: underline;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.spinner {
|
| 123 |
+
border: 3px solid var(--spinner-border);
|
| 124 |
+
border-top: 3px solid var(--theme-color);
|
| 125 |
+
border-radius: 50%;
|
| 126 |
+
width: 14px;
|
| 127 |
+
height: 14px;
|
| 128 |
+
animation: spin 1s linear infinite;
|
| 129 |
+
display: none;
|
| 130 |
+
margin-left: 5px;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
@keyframes spin {
|
| 134 |
+
0% {
|
| 135 |
+
transform: rotate(0deg);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
100% {
|
| 139 |
+
transform: rotate(360deg);
|
| 140 |
+
}
|
| 141 |
+
}
|
chat/static/chat/favicon_io/favicon.ico
ADDED
|
|
chat/static/chat/favicon_io/site.webmanifest
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"short_name": "Devnoms",
|
| 3 |
+
"name": "Devnoms",
|
| 4 |
+
"description": "A powerful chat application for seamless communication.",
|
| 5 |
+
"start_url": "/user/",
|
| 6 |
+
"display": "standalone",
|
| 7 |
+
"background_color": "#000000",
|
| 8 |
+
"theme_color": "#282a2d",
|
| 9 |
+
"icons": [
|
| 10 |
+
{
|
| 11 |
+
"src": "https://pdf813.netlify.app/chat/favicon_io/android-chrome-192x192.png",
|
| 12 |
+
"sizes": "192x192",
|
| 13 |
+
"type": "image/png"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"src": "https://pdf813.netlify.app/chat/favicon_io/android-chrome-512x512.png",
|
| 17 |
+
"sizes": "512x512",
|
| 18 |
+
"type": "image/png"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"src": "https://pdf813.netlify.app/chat/favicon_io/apple-touch-icon.png",
|
| 22 |
+
"sizes": "180x180",
|
| 23 |
+
"type": "image/png"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"src": "https://pdf813.netlify.app/chat/favicon_io/favicon-16x16.png",
|
| 27 |
+
"sizes": "16x16",
|
| 28 |
+
"type": "image/png"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"src": "https://pdf813.netlify.app/chat/favicon_io/favicon-32x32.png",
|
| 32 |
+
"sizes": "32x32",
|
| 33 |
+
"type": "image/png"
|
| 34 |
+
}
|
| 35 |
+
],
|
| 36 |
+
"splash_pages": [
|
| 37 |
+
{
|
| 38 |
+
"src": "https://pdf813.netlify.app/chat/img/logo_750x750.png",
|
| 39 |
+
"sizes": "512x512",
|
| 40 |
+
"type": "image/png"
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"scope": "/",
|
| 44 |
+
"orientation": "portrait",
|
| 45 |
+
"prefer_related_applications": false,
|
| 46 |
+
"related_applications": [],
|
| 47 |
+
"iarc_rating_id": "AAA-0000-0000"
|
| 48 |
+
}
|
| 49 |
+
|
chat/static/chat/js/base.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
document.addEventListener('DOMContentLoaded', function () {
|
| 3 |
+
document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
|
| 4 |
+
});
|
| 5 |
+
|
| 6 |
+
window.addEventListener('resize', function () {
|
| 7 |
+
document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
// Back button functionality
|
| 11 |
+
function goBack() {
|
| 12 |
+
window.history.back();
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// Toggle menu for three dots (Edit Profile, Settings, Logout)
|
| 16 |
+
function toggleMenu() {
|
| 17 |
+
const dropdown = document.getElementById("dropdown");
|
| 18 |
+
dropdown.classList.toggle("show");
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// Close dropdown menu when clicking outside
|
| 22 |
+
document.addEventListener("click", (event) => {
|
| 23 |
+
const dropdown = document.getElementById("dropdown");
|
| 24 |
+
if (!dropdown.contains(event.target) && !event.target.closest(".menu")) {
|
| 25 |
+
dropdown.classList.remove("show");
|
| 26 |
+
}
|
| 27 |
+
});
|
| 28 |
+
// Toggle dark mode
|
| 29 |
+
const darkModeToggle = document.querySelector(".dark-mode-toggle");
|
| 30 |
+
darkModeToggle.addEventListener("click", () => {
|
| 31 |
+
const theme = document.documentElement.getAttribute("data-theme");
|
| 32 |
+
if (theme === "light") {
|
| 33 |
+
darkModeToggle.innerHTML = "Light Mode";
|
| 34 |
+
document.documentElement.setAttribute("data-theme", "dark");
|
| 35 |
+
} else {
|
| 36 |
+
darkModeToggle.innerHTML = "Dark Mode";
|
| 37 |
+
document.documentElement.setAttribute("data-theme", "light");
|
| 38 |
+
}
|
| 39 |
+
});
|
chat/static/chat/js/chat.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const chatArea = document.getElementById("chat-area");
|
| 2 |
+
const messageInput = document.getElementById("message-input");
|
| 3 |
+
const sendButton = document.getElementById("send-button");
|
| 4 |
+
|
| 5 |
+
// Automatically scroll to the bottom of the chat area
|
| 6 |
+
const scrollToBottom = () => {
|
| 7 |
+
chatArea.scrollTop = chatArea.scrollHeight;
|
| 8 |
+
};
|
| 9 |
+
|
| 10 |
+
// Add a new message to the chat area
|
| 11 |
+
const addMessage = (message, type) => {
|
| 12 |
+
const messageDiv = document.createElement("div");
|
| 13 |
+
messageDiv.classList.add("chat-message", type);
|
| 14 |
+
|
| 15 |
+
const textDiv = document.createElement("div");
|
| 16 |
+
textDiv.classList.add("message-text");
|
| 17 |
+
textDiv.innerHTML = message.replace(/\n/g, "<br>"); // Replace line breaks with <br>
|
| 18 |
+
|
| 19 |
+
const timestampDiv = document.createElement("div");
|
| 20 |
+
timestampDiv.classList.add("timestamp");
|
| 21 |
+
const currentTime = new Date();
|
| 22 |
+
timestampDiv.textContent = currentTime.toLocaleTimeString([], {
|
| 23 |
+
hour: "2-digit",
|
| 24 |
+
minute: "2-digit",
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
messageDiv.appendChild(textDiv);
|
| 28 |
+
messageDiv.appendChild(timestampDiv);
|
| 29 |
+
chatArea.appendChild(messageDiv);
|
| 30 |
+
|
| 31 |
+
scrollToBottom();
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
// Send a message when the send button is clicked
|
| 35 |
+
sendButton.addEventListener("click", () => {
|
| 36 |
+
const message = messageInput.value.trim();
|
| 37 |
+
if (message) {
|
| 38 |
+
addMessage(message, "sent");
|
| 39 |
+
messageInput.value = "";
|
| 40 |
+
messageInput.style.height = "46px";
|
| 41 |
+
|
| 42 |
+
// Keep the focus on the textarea
|
| 43 |
+
messageInput.focus();
|
| 44 |
+
}
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
// Auto-expand the textarea as the user types
|
| 48 |
+
messageInput.addEventListener("input", () => {
|
| 49 |
+
messageInput.style.height = "auto";
|
| 50 |
+
messageInput.style.height = `${messageInput.scrollHeight - 4}px`;
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Allow multi-line input using Enter key
|
| 54 |
+
// messageInput.addEventListener('keydown', (event) => {
|
| 55 |
+
// if (event.key === 'Enter' && event.shiftKey) {
|
| 56 |
+
// event.preventDefault();
|
| 57 |
+
// sendButton.click(); // Trigger the send button
|
| 58 |
+
// }
|
| 59 |
+
// });
|
| 60 |
+
scrollToBottom();
|
chat/static/chat/js/home.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const navButtons = document.querySelectorAll(".bottom-nav button");
|
| 2 |
+
const slider = document.createElement("div");
|
| 3 |
+
slider.className = "slider";
|
| 4 |
+
document.querySelector(".bottom-nav").appendChild(slider);
|
| 5 |
+
|
| 6 |
+
// Initialize the slider position to the first active button
|
| 7 |
+
function setSliderPosition(button) {
|
| 8 |
+
const buttonRect = button.getBoundingClientRect();
|
| 9 |
+
const navRect = document.querySelector(".bottom-nav").getBoundingClientRect();
|
| 10 |
+
const leftPosition =
|
| 11 |
+
buttonRect.left -
|
| 12 |
+
navRect.left +
|
| 13 |
+
buttonRect.width / 2 -
|
| 14 |
+
slider.offsetWidth / 2;
|
| 15 |
+
slider.style.left = `${leftPosition}px`;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
navButtons.forEach((button) => {
|
| 19 |
+
button.addEventListener("click", () => {
|
| 20 |
+
navButtons.forEach((btn) => btn.classList.remove("active"));
|
| 21 |
+
button.classList.add("active");
|
| 22 |
+
setSliderPosition(button);
|
| 23 |
+
});
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
+
// Set initial position of the slider
|
| 27 |
+
setSliderPosition(document.querySelector(".bottom-nav .active"));
|
chat/static/chat/js/login.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function togglePasswordVisibility() {
|
| 2 |
+
const passwordField = document.getElementById('password');
|
| 3 |
+
const toggleIcon = document.getElementById('toggle-password');
|
| 4 |
+
|
| 5 |
+
if (passwordField.type === 'password') {
|
| 6 |
+
passwordField.type = 'text';
|
| 7 |
+
toggleIcon.innerHTML = '<img src="https://pdf813.netlify.app/chat/img/icon/icons8-closed-eye-50.png" alt="Hide Password" />';
|
| 8 |
+
|
| 9 |
+
} else {
|
| 10 |
+
passwordField.type = 'password';
|
| 11 |
+
toggleIcon.innerHTML = '<img src="https://pdf813.netlify.app/chat/img/icon/icons8-eye-48.png" alt="Show Password" />';
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// Form validation and error handling
|
| 17 |
+
const form = document.getElementById('login-form');
|
| 18 |
+
const emailInput = document.getElementById('email');
|
| 19 |
+
const passwordInput = document.getElementById('password');
|
| 20 |
+
const emailError = document.getElementById('email-error');
|
| 21 |
+
const passwordError = document.getElementById('password-error');
|
| 22 |
+
const loginBtn = document.getElementById('login-btn');
|
| 23 |
+
|
| 24 |
+
form.addEventListener('submit', function (event) {
|
| 25 |
+
event.preventDefault();
|
| 26 |
+
let hasError = false;
|
| 27 |
+
|
| 28 |
+
// Clear previous error messages
|
| 29 |
+
emailError.textContent = '';
|
| 30 |
+
passwordError.textContent = '';
|
| 31 |
+
|
| 32 |
+
// Validate email
|
| 33 |
+
if (emailInput.value.trim() === '') {
|
| 34 |
+
emailError.textContent = 'Please fill in the email field';
|
| 35 |
+
hasError = true;
|
| 36 |
+
} else if (!validateEmail(emailInput.value)) {
|
| 37 |
+
emailError.textContent = 'Email is not valid';
|
| 38 |
+
hasError = true;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// Validate password
|
| 42 |
+
if (passwordInput.value.trim() === '') {
|
| 43 |
+
passwordError.textContent = 'Please fill in the password field';
|
| 44 |
+
hasError = true;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
if (hasError) return;
|
| 48 |
+
|
| 49 |
+
// Show spinner and disable button
|
| 50 |
+
loginBtn.disabled = true;
|
| 51 |
+
loginBtn.innerHTML = 'Logging in... <div class="spinner" id="spinner"></div>';
|
| 52 |
+
const spinner = document.getElementById('spinner');
|
| 53 |
+
spinner.style.display = 'inline-block';
|
| 54 |
+
|
| 55 |
+
// Simulate an API request for login (replace this with your actual login API call)
|
| 56 |
+
setTimeout(function () {
|
| 57 |
+
// Simulate a response: if login fails
|
| 58 |
+
let loginSuccess = false; // Change this to simulate success/failure
|
| 59 |
+
if (!loginSuccess) {
|
| 60 |
+
emailError.textContent = 'Email not found or incorrect password';
|
| 61 |
+
passwordError.textContent = 'Please try again';
|
| 62 |
+
spinner.style.display = 'none';
|
| 63 |
+
loginBtn.disabled = false;
|
| 64 |
+
loginBtn.innerText = "Login";
|
| 65 |
+
} else {
|
| 66 |
+
window.location.href = '/dashboard'; // Redirect to dashboard after successful login
|
| 67 |
+
}
|
| 68 |
+
}, 3000); // Shortened timeout for testing purposes
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
function validateEmail(email) {
|
| 72 |
+
const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
|
| 73 |
+
return regex.test(email);
|
| 74 |
+
}
|
chat/static/chat/js/profile.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
console.log("profile");
|
chat/static/chat/js/search.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
console.log("search");
|
chat/static/chat/js/settings.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
console.log("settings");
|
chat/static/chat/js/signup.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function togglePasswordVisibility() {
|
| 2 |
+
const passwordField = document.getElementById('password');
|
| 3 |
+
const toggleIcon = document.getElementById('toggle-password');
|
| 4 |
+
|
| 5 |
+
if (passwordField.type === 'password') {
|
| 6 |
+
passwordField.type = 'text';
|
| 7 |
+
toggleIcon.innerHTML = '<img src="https://pdf813.netlify.app/chat/img/icon/icons8-closed-eye-50.png" alt="Hide Password" />';
|
| 8 |
+
|
| 9 |
+
} else {
|
| 10 |
+
passwordField.type = 'password';
|
| 11 |
+
toggleIcon.innerHTML = '<img src="https://pdf813.netlify.app/chat/img/icon/icons8-eye-48.png" alt="Show Password" />';
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// Form validation and error handling
|
| 17 |
+
const form = document.getElementById('signup-form');
|
| 18 |
+
const nameInput = document.getElementById('name');
|
| 19 |
+
const emailInput = document.getElementById('email');
|
| 20 |
+
const passwordInput = document.getElementById('password');
|
| 21 |
+
const confirmPasswordInput = document.getElementById('confirm-password');
|
| 22 |
+
const nameError = document.getElementById('name-error');
|
| 23 |
+
const emailError = document.getElementById('email-error');
|
| 24 |
+
const passwordError = document.getElementById('password-error');
|
| 25 |
+
const confirmPasswordError = document.getElementById('confirm-password-error');
|
| 26 |
+
const signupBtn = document.getElementById('signup-btn');
|
| 27 |
+
|
| 28 |
+
form.addEventListener('submit', function (event) {
|
| 29 |
+
event.preventDefault();
|
| 30 |
+
let hasError = false;
|
| 31 |
+
|
| 32 |
+
// Clear previous error messages
|
| 33 |
+
nameError.textContent = '';
|
| 34 |
+
emailError.textContent = '';
|
| 35 |
+
passwordError.textContent = '';
|
| 36 |
+
confirmPasswordError.textContent = '';
|
| 37 |
+
|
| 38 |
+
// Validate name
|
| 39 |
+
if (nameInput.value.trim() === '') {
|
| 40 |
+
nameError.textContent = 'Please fill in the username field';
|
| 41 |
+
hasError = true;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// Validate email
|
| 45 |
+
if (emailInput.value.trim() === '') {
|
| 46 |
+
emailError.textContent = 'Please fill in the email field';
|
| 47 |
+
hasError = true;
|
| 48 |
+
} else if (!validateEmail(emailInput.value)) {
|
| 49 |
+
emailError.textContent = 'Email is not valid';
|
| 50 |
+
hasError = true;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// Validate password
|
| 54 |
+
if (passwordInput.value.trim() === '') {
|
| 55 |
+
passwordError.textContent = 'Please fill in the password field';
|
| 56 |
+
hasError = true;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Validate confirm password
|
| 60 |
+
if (confirmPasswordInput.value !== passwordInput.value) {
|
| 61 |
+
confirmPasswordError.textContent = 'Passwords do not match';
|
| 62 |
+
hasError = true;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
if (hasError) return;
|
| 66 |
+
|
| 67 |
+
// Show spinner and disable button
|
| 68 |
+
signupBtn.disabled = true;
|
| 69 |
+
signupBtn.innerHTML = 'Signing Up... <div class="spinner" id="spinner"></div>';
|
| 70 |
+
const spinner = document.getElementById('spinner');
|
| 71 |
+
spinner.style.display = 'inline-block';
|
| 72 |
+
|
| 73 |
+
// Simulate an API request for signup (replace this with your actual signup API call)
|
| 74 |
+
setTimeout(function () {
|
| 75 |
+
// Simulate a response: if signup fails
|
| 76 |
+
let signupSuccess = false; // Change this to simulate success/failure
|
| 77 |
+
if (!signupSuccess) {
|
| 78 |
+
emailError.textContent = 'Email is already taken';
|
| 79 |
+
spinner.style.display = 'none';
|
| 80 |
+
signupBtn.disabled = false;
|
| 81 |
+
signupBtn.innerText = "Sign Up";
|
| 82 |
+
} else {
|
| 83 |
+
window.location.href = '/login'; // Redirect to login after successful signup
|
| 84 |
+
}
|
| 85 |
+
}, 3000); // Shortened timeout for testing purposes
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
function validateEmail(email) {
|
| 89 |
+
const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2, 6}$/;
|
| 90 |
+
return regex.test(email);
|
| 91 |
+
}
|
chat/tests.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.test import TestCase
|
| 2 |
+
|
| 3 |
+
# Create your tests here.
|
chat/views/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .chat_view import *
|
| 2 |
+
from .friends_view import *
|
| 3 |
+
from .home_view import *
|
| 4 |
+
from .user_view import *
|
| 5 |
+
from .password_view import *
|
| 6 |
+
from .test_view import *
|
chat/views/chat_view.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
from django.contrib.auth.decorators import login_required
|
| 4 |
+
from chat.models import UserRelation, Messages
|
| 5 |
+
from django.http.response import JsonResponse
|
| 6 |
+
from django.contrib import messages as django_messages
|
| 7 |
+
from django.db.models import Q # Import Q
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@login_required(login_url="login")
|
| 11 |
+
def chat(request, username):
|
| 12 |
+
usersen = request.user
|
| 13 |
+
try:
|
| 14 |
+
# Get the friend and the relation in one go
|
| 15 |
+
friend = User.objects.get(username=username)
|
| 16 |
+
relation = UserRelation.objects.get(user=usersen, friend=friend, accepted=True)
|
| 17 |
+
except (User.DoesNotExist, UserRelation.DoesNotExist):
|
| 18 |
+
# If user or relation doesn't exist, show error message and redirect
|
| 19 |
+
django_messages.error(
|
| 20 |
+
request, "User or relation not found. You cannot chat with this user."
|
| 21 |
+
)
|
| 22 |
+
return redirect("home")
|
| 23 |
+
|
| 24 |
+
# Retrieve messages in one query, sorted by timestamp
|
| 25 |
+
messages = Messages.objects.filter(
|
| 26 |
+
(Q(sender_name=usersen) & Q(receiver_name=friend))
|
| 27 |
+
| (Q(sender_name=friend) & Q(receiver_name=usersen))
|
| 28 |
+
).order_by("timestamp")
|
| 29 |
+
|
| 30 |
+
# Handle GET method to render the chat
|
| 31 |
+
if request.method == "GET":
|
| 32 |
+
return render(
|
| 33 |
+
request,
|
| 34 |
+
"chat.html",
|
| 35 |
+
{
|
| 36 |
+
"relation_key": relation.relation_key,
|
| 37 |
+
"messages": messages,
|
| 38 |
+
"curr_user": usersen,
|
| 39 |
+
"friend": friend,
|
| 40 |
+
},
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# If the method is not GET, return an error response
|
| 44 |
+
return JsonResponse({"error": "Invalid request method"}, status=405)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# @login_required(login_url="login")
|
| 48 |
+
# @csrf_exempt
|
| 49 |
+
# def message_list(request, sender=None, receiver=None):
|
| 50 |
+
# if request.method == "GET":
|
| 51 |
+
# messages = Messages.objects.filter(
|
| 52 |
+
# sender_name=sender, receiver_name=receiver, seen=False
|
| 53 |
+
# )
|
| 54 |
+
# serializer = MessageSerializer(
|
| 55 |
+
# messages, many=True, context={"request": request}
|
| 56 |
+
# )
|
| 57 |
+
# for message in messages:
|
| 58 |
+
# message.seen = True
|
| 59 |
+
# message.save()
|
| 60 |
+
# return JsonResponse(serializer.data, safe=False)
|
| 61 |
+
|
| 62 |
+
# elif request.method == "POST":
|
| 63 |
+
# data = JSONParser().parse(request)
|
| 64 |
+
# serializer = MessageSerializer(data=data)
|
| 65 |
+
# if serializer.is_valid():
|
| 66 |
+
# serializer.save()
|
| 67 |
+
# return JsonResponse(serializer.data, status=201)
|
| 68 |
+
# return JsonResponse(serializer.errors, status=400)
|
chat/views/friends_view.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
from django.contrib.auth.decorators import login_required
|
| 4 |
+
from chat.models import UserRelation
|
| 5 |
+
from django.contrib import messages
|
| 6 |
+
from django.http import HttpResponseRedirect
|
| 7 |
+
from django.urls import reverse
|
| 8 |
+
|
| 9 |
+
@login_required(login_url="login")
|
| 10 |
+
def delete_friend(request):
|
| 11 |
+
if request.method == "POST":
|
| 12 |
+
username = request.POST.get("username")
|
| 13 |
+
requestDelete = request.POST.get("requestDelete")
|
| 14 |
+
user = request.user
|
| 15 |
+
friend = User.objects.get(username=username)
|
| 16 |
+
try:
|
| 17 |
+
# print("starts")
|
| 18 |
+
exists = UserRelation.objects.filter(user=user, friend=friend).exists()
|
| 19 |
+
if requestDelete == "True":
|
| 20 |
+
reverse_exists = UserRelation.objects.filter(
|
| 21 |
+
user=friend, friend=user
|
| 22 |
+
).exists()
|
| 23 |
+
if reverse_exists:
|
| 24 |
+
return HttpResponseRedirect(
|
| 25 |
+
request.META.get("HTTP_REFERER", reverse("home"))
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# print("sts")
|
| 29 |
+
if exists:
|
| 30 |
+
pass
|
| 31 |
+
else:
|
| 32 |
+
return HttpResponseRedirect(
|
| 33 |
+
request.META.get("HTTP_REFERER", reverse("home"))
|
| 34 |
+
)
|
| 35 |
+
user_relation = UserRelation.objects.get(user=user, friend=friend)
|
| 36 |
+
user_relation.delete()
|
| 37 |
+
|
| 38 |
+
user_relation_reverse = UserRelation.objects.get(user=friend, friend=user)
|
| 39 |
+
user_relation_reverse.delete()
|
| 40 |
+
messages.success(request, "Friend deleted successfully.")
|
| 41 |
+
|
| 42 |
+
except UserRelation.DoesNotExist:
|
| 43 |
+
messages.success(request, "Request deleted successfully.")
|
| 44 |
+
pass
|
| 45 |
+
return redirect("home")
|
| 46 |
+
else:
|
| 47 |
+
return redirect("home")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@login_required(login_url="login")
|
| 51 |
+
def accept_request(request):
|
| 52 |
+
if request.method == "POST":
|
| 53 |
+
username = request.POST.get("username")
|
| 54 |
+
user = request.user
|
| 55 |
+
friend = User.objects.get(username=username)
|
| 56 |
+
accepted = True
|
| 57 |
+
|
| 58 |
+
exists = UserRelation.objects.filter(user=user, friend=friend).exists()
|
| 59 |
+
reverse_exists = UserRelation.objects.filter(user=friend, friend=user).exists()
|
| 60 |
+
|
| 61 |
+
# print("sts")
|
| 62 |
+
if exists:
|
| 63 |
+
return HttpResponseRedirect(
|
| 64 |
+
request.META.get("HTTP_REFERER", reverse("home"))
|
| 65 |
+
)
|
| 66 |
+
if not reverse_exists:
|
| 67 |
+
return HttpResponseRedirect(
|
| 68 |
+
request.META.get("HTTP_REFERER", reverse("home"))
|
| 69 |
+
)
|
| 70 |
+
relation_key = username + "_" + user.username
|
| 71 |
+
user_relation = UserRelation(
|
| 72 |
+
user=user, friend=friend, accepted=accepted, relation_key=relation_key
|
| 73 |
+
)
|
| 74 |
+
user_relation.save()
|
| 75 |
+
|
| 76 |
+
user_relation_revrse = UserRelation.objects.get(user=friend, friend=user)
|
| 77 |
+
user_relation_revrse.accepted = True
|
| 78 |
+
user_relation_revrse.relation_key = relation_key
|
| 79 |
+
user_relation_revrse.save()
|
| 80 |
+
messages.success(request, "Friend Added successfully.")
|
| 81 |
+
|
| 82 |
+
return redirect("home")
|
| 83 |
+
else:
|
| 84 |
+
return redirect("home")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@login_required(login_url="login")
|
| 88 |
+
def add_friend(request):
|
| 89 |
+
if request.method == "POST":
|
| 90 |
+
username = request.POST.get("username")
|
| 91 |
+
user = request.user
|
| 92 |
+
friend = User.objects.get(username=username)
|
| 93 |
+
accepted = False
|
| 94 |
+
# print("starts")
|
| 95 |
+
exists = UserRelation.objects.filter(user=user, friend=friend).exists()
|
| 96 |
+
reverse_exists = UserRelation.objects.filter(user=friend, friend=user).exists()
|
| 97 |
+
# print("sts")
|
| 98 |
+
if exists or reverse_exists:
|
| 99 |
+
# print("star")
|
| 100 |
+
return HttpResponseRedirect(
|
| 101 |
+
request.META.get("HTTP_REFERER", reverse("home"))
|
| 102 |
+
)
|
| 103 |
+
user_relation = UserRelation(user=user, friend=friend, accepted=accepted)
|
| 104 |
+
user_relation.save()
|
| 105 |
+
messages.success(request, "Request sended successfully.")
|
| 106 |
+
|
| 107 |
+
return redirect("home")
|
| 108 |
+
else:
|
| 109 |
+
return redirect("home")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@login_required(login_url="login")
|
| 113 |
+
def search(request):
|
| 114 |
+
if request.method == "GET":
|
| 115 |
+
query = request.GET.get("q", "")
|
| 116 |
+
if query:
|
| 117 |
+
users = User.objects.filter(username__icontains=query)
|
| 118 |
+
if users:
|
| 119 |
+
return render(
|
| 120 |
+
request,
|
| 121 |
+
"search.html",
|
| 122 |
+
{"query": query, "users": users, "user": request.user.username},
|
| 123 |
+
)
|
| 124 |
+
else:
|
| 125 |
+
not_found_message = f'No users found for "{query}"'
|
| 126 |
+
return render(
|
| 127 |
+
request,
|
| 128 |
+
"search.html",
|
| 129 |
+
{
|
| 130 |
+
"query": query,
|
| 131 |
+
"not_found_message": not_found_message,
|
| 132 |
+
},
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
return render(request, "search.html", {"user": request.user.username})
|
| 136 |
+
|
chat/views/home_view.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
from django.contrib.auth.decorators import login_required
|
| 4 |
+
from chat.models import UserRelation
|
| 5 |
+
from django.contrib import messages
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@login_required(login_url="login")
|
| 9 |
+
def userprofile(request, username):
|
| 10 |
+
if username == request.user.username:
|
| 11 |
+
return redirect("/")
|
| 12 |
+
friend_dict = {}
|
| 13 |
+
request_dict = {}
|
| 14 |
+
friend_dict["accepted"] = False
|
| 15 |
+
request_dict["accepted"] = False
|
| 16 |
+
friend_dict["name"] = ""
|
| 17 |
+
send_request = False
|
| 18 |
+
not_accepted = False
|
| 19 |
+
me_not_accepted = False
|
| 20 |
+
is_friend = False
|
| 21 |
+
try:
|
| 22 |
+
user = User.objects.get(username=username)
|
| 23 |
+
friends_data = UserRelation.objects.all()
|
| 24 |
+
for obj in friends_data:
|
| 25 |
+
if obj.user.username == request.user.username:
|
| 26 |
+
if obj.friend.username == username:
|
| 27 |
+
friend_dict = {
|
| 28 |
+
"name": obj.friend.username,
|
| 29 |
+
"accepted": obj.accepted,
|
| 30 |
+
}
|
| 31 |
+
for obj in friends_data:
|
| 32 |
+
if obj.friend.username == request.user.username:
|
| 33 |
+
if obj.user.username == username:
|
| 34 |
+
if obj.accepted:
|
| 35 |
+
me_not_accepted = False
|
| 36 |
+
else:
|
| 37 |
+
me_not_accepted = True
|
| 38 |
+
|
| 39 |
+
except User.DoesNotExist:
|
| 40 |
+
messages.error(request, "User does not exist.")
|
| 41 |
+
return render(request, "friend.html")
|
| 42 |
+
|
| 43 |
+
if friend_dict["name"] == "":
|
| 44 |
+
if me_not_accepted == True:
|
| 45 |
+
pass
|
| 46 |
+
# print("me not accepted")
|
| 47 |
+
else:
|
| 48 |
+
# print("not a friend")
|
| 49 |
+
send_request = True
|
| 50 |
+
|
| 51 |
+
elif friend_dict["accepted"] == False:
|
| 52 |
+
# print("not_accepted")
|
| 53 |
+
not_accepted = True
|
| 54 |
+
|
| 55 |
+
else:
|
| 56 |
+
# print("friend")
|
| 57 |
+
is_friend = True
|
| 58 |
+
# print("send_request = ", send_request)
|
| 59 |
+
# print("not_accepted = ", not_accepted)
|
| 60 |
+
# print("me_not_accepted = ", me_not_accepted)
|
| 61 |
+
# print("is_friend = ", is_friend)
|
| 62 |
+
# You can now access user details, such as username, email, etc.
|
| 63 |
+
user_details = {
|
| 64 |
+
"username": user.username,
|
| 65 |
+
"email": user.email,
|
| 66 |
+
"send_request": send_request,
|
| 67 |
+
"not_accepted": not_accepted,
|
| 68 |
+
"is_friend": is_friend,
|
| 69 |
+
"me_not_accepted": me_not_accepted,
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
return render(request, "friend.html", {"user_details": user_details})
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
from django.shortcuts import render, redirect
|
| 76 |
+
from django.contrib.auth.models import User
|
| 77 |
+
from django.contrib.auth.decorators import login_required
|
| 78 |
+
from chat.models import UserRelation
|
| 79 |
+
from django.contrib import messages
|
| 80 |
+
@login_required(login_url="login")
|
| 81 |
+
def HomePage(request):
|
| 82 |
+
friends_data = UserRelation.objects.all()
|
| 83 |
+
friends_list = []
|
| 84 |
+
for obj in friends_data:
|
| 85 |
+
if obj.user.username == request.user.username:
|
| 86 |
+
friend_dict = {"username": obj.friend.username, "accepted": obj.accepted}
|
| 87 |
+
friends_list.append(friend_dict)
|
| 88 |
+
|
| 89 |
+
request_list = []
|
| 90 |
+
for obj in friends_data:
|
| 91 |
+
if obj.friend.username == request.user.username:
|
| 92 |
+
if not obj.accepted:
|
| 93 |
+
request_dict = {"username": obj.user.username}
|
| 94 |
+
request_list.append(request_dict)
|
| 95 |
+
|
| 96 |
+
data = {
|
| 97 |
+
"email": request.user.email,
|
| 98 |
+
"username": request.user.username,
|
| 99 |
+
"friends": friends_list,
|
| 100 |
+
"requests": request_list,
|
| 101 |
+
}
|
| 102 |
+
return render(
|
| 103 |
+
request,
|
| 104 |
+
"home.html",
|
| 105 |
+
{
|
| 106 |
+
"data": data,
|
| 107 |
+
},
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
chat/views/password_view.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib.auth.models import User
|
| 2 |
+
from django.contrib.auth import views as auth_views
|
| 3 |
+
|
| 4 |
+
class CustomPasswordResetView(auth_views.PasswordResetView):
|
| 5 |
+
template_name = './password/password_reset_form.html'
|
| 6 |
+
email_template_name = './password/password_reset_email.html'
|
| 7 |
+
|
| 8 |
+
def form_valid(self, form):
|
| 9 |
+
email = form.cleaned_data['email']
|
| 10 |
+
if not User.objects.filter(email=email).exists():
|
| 11 |
+
form.add_error(
|
| 12 |
+
'email', 'This email is not associated with any user in our system.')
|
| 13 |
+
return self.form_invalid(form)
|
| 14 |
+
return super().form_valid(form)
|
chat/views/test_view.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test(request):
|
| 5 |
+
|
| 6 |
+
return render(
|
| 7 |
+
request,
|
| 8 |
+
"./test/dev.html",
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_chat(request):
|
| 13 |
+
|
| 14 |
+
return render(
|
| 15 |
+
request,
|
| 16 |
+
"./test/chat.html",
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_home(request):
|
| 21 |
+
|
| 22 |
+
return render(
|
| 23 |
+
request,
|
| 24 |
+
"./test/home.html",
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_login(request):
|
| 29 |
+
|
| 30 |
+
return render(
|
| 31 |
+
request,
|
| 32 |
+
"./test/login.html",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_signup(request):
|
| 37 |
+
|
| 38 |
+
return render(
|
| 39 |
+
request,
|
| 40 |
+
"./test/signup.html",
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def test_profile(request):
|
| 45 |
+
|
| 46 |
+
return render(
|
| 47 |
+
request,
|
| 48 |
+
"./test/profile.html",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_search(request):
|
| 53 |
+
|
| 54 |
+
return render(
|
| 55 |
+
request,
|
| 56 |
+
"./test/search.html",
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def test_settings(request):
|
| 61 |
+
|
| 62 |
+
return render(
|
| 63 |
+
request,
|
| 64 |
+
"./test/settings.html",
|
| 65 |
+
)
|
chat/views/user_view.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect
|
| 2 |
+
from django.contrib.auth.models import User
|
| 3 |
+
from django.contrib.auth import authenticate, login, logout
|
| 4 |
+
from django.contrib.auth.decorators import login_required
|
| 5 |
+
from django.db.models import Q
|
| 6 |
+
|
| 7 |
+
@login_required(login_url="login")
|
| 8 |
+
def EditProfile(request):
|
| 9 |
+
success_message = None
|
| 10 |
+
error_message = None
|
| 11 |
+
|
| 12 |
+
if request.method == "POST":
|
| 13 |
+
new_email = request.POST.get("email")
|
| 14 |
+
new_username = request.POST.get("username")
|
| 15 |
+
|
| 16 |
+
# Check if the new username is already taken
|
| 17 |
+
if (
|
| 18 |
+
new_username != request.user.username
|
| 19 |
+
and User.objects.filter(username=new_username).exists()
|
| 20 |
+
):
|
| 21 |
+
error_message = "Username already exists. Please choose a different one."
|
| 22 |
+
elif (
|
| 23 |
+
new_email != request.user.email
|
| 24 |
+
and User.objects.filter(email=new_email).exists()
|
| 25 |
+
):
|
| 26 |
+
error_message = "Email address already associated with another account. Please choose a different one."
|
| 27 |
+
else:
|
| 28 |
+
# Update email and username
|
| 29 |
+
# print(request.user.id)
|
| 30 |
+
request.user.email = new_email
|
| 31 |
+
request.user.username = new_username
|
| 32 |
+
request.user.save()
|
| 33 |
+
success_message = "Profile updated successfully."
|
| 34 |
+
|
| 35 |
+
# Pre-fill the form with the user's existing data
|
| 36 |
+
initial_data = {
|
| 37 |
+
"email": request.user.email,
|
| 38 |
+
"username": request.user.username,
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
return render(
|
| 42 |
+
request,
|
| 43 |
+
"edit.html",
|
| 44 |
+
{
|
| 45 |
+
"initial_data": initial_data,
|
| 46 |
+
"success_message": success_message,
|
| 47 |
+
"error_message": error_message,
|
| 48 |
+
},
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def SignupPage(request):
|
| 53 |
+
if request.user.is_authenticated:
|
| 54 |
+
return redirect("home")
|
| 55 |
+
error_message = "" # Initialize error_message as None
|
| 56 |
+
|
| 57 |
+
if request.method == "POST":
|
| 58 |
+
uname = request.POST.get("username")
|
| 59 |
+
email = request.POST.get("email")
|
| 60 |
+
pass1 = request.POST.get("password1")
|
| 61 |
+
pass2 = request.POST.get("password2")
|
| 62 |
+
|
| 63 |
+
data = {
|
| 64 |
+
"username": uname,
|
| 65 |
+
"useremail": email,
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# Check if a user with the same email or username already exists
|
| 69 |
+
if User.objects.filter(username=uname).exists():
|
| 70 |
+
error_message = "A user with the same username already exists."
|
| 71 |
+
return render(
|
| 72 |
+
request,
|
| 73 |
+
"signup.html",
|
| 74 |
+
{"error_message": error_message, "userdata": data},
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
elif User.objects.filter(email=email).exists():
|
| 78 |
+
error_message = "A user with the same email already exists."
|
| 79 |
+
return render(
|
| 80 |
+
request,
|
| 81 |
+
"signup.html",
|
| 82 |
+
{"error_message": error_message, "userdata": data},
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
else:
|
| 86 |
+
# Create the user
|
| 87 |
+
user = User.objects.create_user(username=uname, email=email, password=pass1)
|
| 88 |
+
user.save()
|
| 89 |
+
# Log the user in after registration
|
| 90 |
+
login(request, user)
|
| 91 |
+
return redirect("home")
|
| 92 |
+
|
| 93 |
+
return render(request, "signup.html", {"error_message": error_message})
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def LoginPage(request):
|
| 97 |
+
if request.user.is_authenticated:
|
| 98 |
+
return redirect("home")
|
| 99 |
+
error_message = ""
|
| 100 |
+
email_or_username = ""
|
| 101 |
+
if request.method == "POST":
|
| 102 |
+
email_or_username = request.POST.get("email_or_username")
|
| 103 |
+
pass1 = request.POST.get("pass")
|
| 104 |
+
try:
|
| 105 |
+
curr_user = User.objects.get(Q(email=email_or_username) | Q(username=email_or_username))
|
| 106 |
+
except User.DoesNotExist:
|
| 107 |
+
error_message = "User not found. Please check your Email/Username"
|
| 108 |
+
return render(
|
| 109 |
+
request, "login.html", {"error_message": error_message, "email": email_or_username}
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
user = authenticate(request, username=curr_user.username, password=pass1)
|
| 113 |
+
if user is not None:
|
| 114 |
+
login(request, user)
|
| 115 |
+
return redirect("home")
|
| 116 |
+
else:
|
| 117 |
+
if User.objects.filter(username=curr_user.username).exists():
|
| 118 |
+
error_message = "Incorrect password. Please try again."
|
| 119 |
+
else:
|
| 120 |
+
error_message = "Email not found. Please check your email."
|
| 121 |
+
return render(
|
| 122 |
+
request, "login.html", {"error_message": error_message, "email": email_or_username}
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def LogoutPage(request):
|
| 127 |
+
logout(request)
|
| 128 |
+
return redirect("login")
|
devnoms/__init__.py
ADDED
|
File without changes
|
devnoms/asgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from django.core.asgi import get_asgi_application
|
| 3 |
+
from channels.routing import ProtocolTypeRouter, URLRouter
|
| 4 |
+
from channels.auth import AuthMiddlewareStack
|
| 5 |
+
import chat.routing
|
| 6 |
+
|
| 7 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'devnoms.settings')
|
| 8 |
+
|
| 9 |
+
application = ProtocolTypeRouter({
|
| 10 |
+
"http": get_asgi_application(),
|
| 11 |
+
"websocket": AuthMiddlewareStack(
|
| 12 |
+
URLRouter(
|
| 13 |
+
chat.routing.websocket_urlpatterns
|
| 14 |
+
)
|
| 15 |
+
),
|
| 16 |
+
})
|
devnoms/settings.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
DEVELOPMENT = "live"
|
| 8 |
+
# DEVELOPMENT = "local"
|
| 9 |
+
DEBUG = True
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
if DEVELOPMENT == "local":
|
| 13 |
+
# Development
|
| 14 |
+
DEBUG = True
|
| 15 |
+
DATABASES = {
|
| 16 |
+
"default": {
|
| 17 |
+
"ENGINE": "django.db.backends.postgresql",
|
| 18 |
+
"NAME": "devnoms_localdb",
|
| 19 |
+
"USER": os.getenv("DB_USER_LOCAL"),
|
| 20 |
+
"PASSWORD": os.getenv("DB_PASS_LOCAL"),
|
| 21 |
+
"HOST": os.getenv("DB_HOST_LOCAL"),
|
| 22 |
+
"PORT": "",
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
SITE_URL = "http://localhost:8000"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
else:
|
| 29 |
+
# Live
|
| 30 |
+
# DEBUG = False
|
| 31 |
+
DATABASES = {
|
| 32 |
+
"default": {
|
| 33 |
+
"ENGINE": "django.db.backends.postgresql",
|
| 34 |
+
"NAME": os.getenv("DB_NAME_LIVE"),
|
| 35 |
+
"USER": os.getenv("DB_USER_LIVE"),
|
| 36 |
+
"PASSWORD": os.getenv("DB_PASS_LIVE"),
|
| 37 |
+
"HOST": os.getenv("DB_HOST_LIVE"),
|
| 38 |
+
"PORT": "",
|
| 39 |
+
"OPTIONS": {
|
| 40 |
+
"sslmode": "require",
|
| 41 |
+
},
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
SITE_URL = "https://devnoms.onrender.com"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
SECRET_KEY = os.getenv("SECRET_KEY")
|
| 51 |
+
ALLOWED_HOSTS = ["*"]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
INSTALLED_APPS = [
|
| 55 |
+
"channels",
|
| 56 |
+
"django.contrib.admin",
|
| 57 |
+
"django.contrib.auth",
|
| 58 |
+
"django.contrib.contenttypes",
|
| 59 |
+
"django.contrib.sessions",
|
| 60 |
+
"django.contrib.messages",
|
| 61 |
+
"django.contrib.staticfiles",
|
| 62 |
+
"chat",
|
| 63 |
+
"corsheaders",
|
| 64 |
+
]
|
| 65 |
+
ASGI_APPLICATION = "devnoms.asgi.application"
|
| 66 |
+
|
| 67 |
+
CHANNEL_LAYERS = {
|
| 68 |
+
"default": {
|
| 69 |
+
"BACKEND": "channels.layers.InMemoryChannelLayer",
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
MIDDLEWARE = [
|
| 75 |
+
"django.middleware.security.SecurityMiddleware",
|
| 76 |
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
| 77 |
+
"django.middleware.common.CommonMiddleware",
|
| 78 |
+
"django.middleware.csrf.CsrfViewMiddleware",
|
| 79 |
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
| 80 |
+
"django.contrib.messages.middleware.MessageMiddleware",
|
| 81 |
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
| 82 |
+
"corsheaders.middleware.CorsMiddleware",
|
| 83 |
+
]
|
| 84 |
+
|
| 85 |
+
ROOT_URLCONF = "devnoms.urls"
|
| 86 |
+
|
| 87 |
+
TEMPLATES = [
|
| 88 |
+
{
|
| 89 |
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
| 90 |
+
"DIRS": ["templates"],
|
| 91 |
+
"APP_DIRS": True,
|
| 92 |
+
"OPTIONS": {
|
| 93 |
+
"context_processors": [
|
| 94 |
+
"django.template.context_processors.debug",
|
| 95 |
+
"django.template.context_processors.request",
|
| 96 |
+
"django.contrib.auth.context_processors.auth",
|
| 97 |
+
"django.contrib.messages.context_processors.messages",
|
| 98 |
+
],
|
| 99 |
+
},
|
| 100 |
+
},
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
WSGI_APPLICATION = "devnoms.wsgi.application"
|
| 104 |
+
|
| 105 |
+
AUTH_PASSWORD_VALIDATORS = [
|
| 106 |
+
{
|
| 107 |
+
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
| 117 |
+
},
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
LANGUAGE_CODE = "en-us"
|
| 122 |
+
|
| 123 |
+
# TIME_ZONE = "UTC"
|
| 124 |
+
|
| 125 |
+
TIME_ZONE = "Asia/Kolkata"
|
| 126 |
+
|
| 127 |
+
USE_I18N = True
|
| 128 |
+
|
| 129 |
+
USE_L10N = True
|
| 130 |
+
|
| 131 |
+
USE_TZ = True
|
| 132 |
+
|
| 133 |
+
LOGIN_URL = "login"
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
STATIC_URL = "/static/"
|
| 137 |
+
|
| 138 |
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
| 139 |
+
|
| 140 |
+
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
| 141 |
+
|
| 142 |
+
CORS_ALLOWED_ORIGINS = [
|
| 143 |
+
"http://127.0.0.1:5500",
|
| 144 |
+
"http://localhost:3000",
|
| 145 |
+
"https://devnoms.onrender.com",
|
| 146 |
+
"https://django-chat-application.onrender.com",
|
| 147 |
+
"https://coders813-django-chat-application.hf.space"
|
| 148 |
+
]
|
| 149 |
+
CORS_ALLOW_ALL_ORIGINS = True
|
| 150 |
+
CSRF_TRUSTED_ORIGINS = [
|
| 151 |
+
"https://devnoms.onrender.com",
|
| 152 |
+
"https://django-chat-application.onrender.com",
|
| 153 |
+
"https://coders813-django-chat-application.hf.space"
|
| 154 |
+
]
|
| 155 |
+
|
| 156 |
+
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
| 157 |
+
EMAIL_HOST = "smtp.gmail.com"
|
| 158 |
+
EMAIL_PORT = 587
|
| 159 |
+
EMAIL_USE_TLS = True
|
| 160 |
+
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
|
| 161 |
+
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
|
devnoms/urls.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from chat.views import *
|
| 3 |
+
from django.urls import path
|
| 4 |
+
from django.conf import settings
|
| 5 |
+
from django.contrib.staticfiles.urls import static
|
| 6 |
+
from django.contrib.auth import views as auth_views
|
| 7 |
+
|
| 8 |
+
urlpatterns = [
|
| 9 |
+
path("admin/", admin.site.urls),
|
| 10 |
+
path("", LoginPage, name="login"),
|
| 11 |
+
path("signup/", SignupPage, name="signup"),
|
| 12 |
+
path("logout/", LogoutPage, name="logout"),
|
| 13 |
+
path("user/", HomePage, name="home"),
|
| 14 |
+
path("edit/", EditProfile, name="edit"),
|
| 15 |
+
path("user/<str:username>/", userprofile, name="username"),
|
| 16 |
+
path("add_friend/", add_friend, name="add_friend"),
|
| 17 |
+
path("accept_request/", accept_request, name="accept_request"),
|
| 18 |
+
path("delete_friend/", delete_friend, name="delete_friend"),
|
| 19 |
+
path("search/", search, name="search"),
|
| 20 |
+
# testing
|
| 21 |
+
path("test/", test, name="test"),
|
| 22 |
+
path("test/chat/", test_chat, name="test_chat"),
|
| 23 |
+
path("test/home/", test_home, name="test_home"),
|
| 24 |
+
path("test/login/", test_login, name="test_login"),
|
| 25 |
+
path("test/signup/", test_signup, name="test_signup"),
|
| 26 |
+
path("test/search/", test_search, name="test_search"),
|
| 27 |
+
path("test/profile/", test_profile, name="test_profile"),
|
| 28 |
+
path("test/settings/", test_settings, name="test_settings"),
|
| 29 |
+
# re_path(r"^.*/$", RedirectView.as_view(pattern_name="login", permanent=False)),
|
| 30 |
+
path("chat/<str:username>/", chat, name="chat"),
|
| 31 |
+
path(
|
| 32 |
+
"password-reset/",
|
| 33 |
+
CustomPasswordResetView.as_view(),
|
| 34 |
+
name="password_reset",
|
| 35 |
+
),
|
| 36 |
+
path(
|
| 37 |
+
"password-reset/done/",
|
| 38 |
+
auth_views.PasswordResetDoneView.as_view(
|
| 39 |
+
template_name="./password/password_reset_done.html"
|
| 40 |
+
),
|
| 41 |
+
name="password_reset_done",
|
| 42 |
+
),
|
| 43 |
+
path(
|
| 44 |
+
"reset/<uidb64>/<token>/",
|
| 45 |
+
auth_views.PasswordResetConfirmView.as_view(
|
| 46 |
+
template_name="./password/password_reset_confirm.html"
|
| 47 |
+
),
|
| 48 |
+
name="password_reset_confirm",
|
| 49 |
+
),
|
| 50 |
+
path(
|
| 51 |
+
"reset/done/",
|
| 52 |
+
auth_views.PasswordResetCompleteView.as_view(
|
| 53 |
+
template_name="./password/password_reset_complete.html"
|
| 54 |
+
),
|
| 55 |
+
name="password_reset_complete",
|
| 56 |
+
),
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
if settings.DEBUG:
|
| 61 |
+
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
devnoms/wsgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WSGI config for devnoms project.
|
| 3 |
+
|
| 4 |
+
It exposes the WSGI callable as a module-level variable named ``application``.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from django.core.wsgi import get_wsgi_application
|
| 13 |
+
|
| 14 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'devnoms.settings')
|
| 15 |
+
|
| 16 |
+
application = get_wsgi_application()
|
manage.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Django's command-line utility for administrative tasks."""
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
"""Run administrative tasks."""
|
| 9 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'devnoms.settings')
|
| 10 |
+
try:
|
| 11 |
+
from django.core.management import execute_from_command_line
|
| 12 |
+
except ImportError as exc:
|
| 13 |
+
raise ImportError(
|
| 14 |
+
"Couldn't import Django. Are you sure it's installed and "
|
| 15 |
+
"available on your PYTHONPATH environment variable? Did you "
|
| 16 |
+
"forget to activate a virtual environment?"
|
| 17 |
+
) from exc
|
| 18 |
+
execute_from_command_line(sys.argv)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == '__main__':
|
| 22 |
+
main()
|
requirements.txt
ADDED
|
Binary file (1.21 kB). View file
|
|
|
static/admin/css/autocomplete.css
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
select.admin-autocomplete {
|
| 2 |
+
width: 20em;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.select2-container--admin-autocomplete.select2-container {
|
| 6 |
+
min-height: 30px;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
.select2-container--admin-autocomplete .select2-selection--single,
|
| 10 |
+
.select2-container--admin-autocomplete .select2-selection--multiple {
|
| 11 |
+
min-height: 30px;
|
| 12 |
+
padding: 0;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
|
| 16 |
+
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
|
| 17 |
+
border-color: var(--body-quiet-color);
|
| 18 |
+
min-height: 30px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
|
| 22 |
+
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
|
| 23 |
+
padding: 0;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
|
| 27 |
+
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
|
| 28 |
+
padding: 0;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.select2-container--admin-autocomplete .select2-selection--single {
|
| 32 |
+
background-color: var(--body-bg);
|
| 33 |
+
border: 1px solid var(--border-color);
|
| 34 |
+
border-radius: 4px;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
|
| 38 |
+
color: var(--body-fg);
|
| 39 |
+
line-height: 30px;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
|
| 43 |
+
cursor: pointer;
|
| 44 |
+
float: right;
|
| 45 |
+
font-weight: bold;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
|
| 49 |
+
color: var(--body-quiet-color);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
|
| 53 |
+
height: 26px;
|
| 54 |
+
position: absolute;
|
| 55 |
+
top: 1px;
|
| 56 |
+
right: 1px;
|
| 57 |
+
width: 20px;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
|
| 61 |
+
border-color: #888 transparent transparent transparent;
|
| 62 |
+
border-style: solid;
|
| 63 |
+
border-width: 5px 4px 0 4px;
|
| 64 |
+
height: 0;
|
| 65 |
+
left: 50%;
|
| 66 |
+
margin-left: -4px;
|
| 67 |
+
margin-top: -2px;
|
| 68 |
+
position: absolute;
|
| 69 |
+
top: 50%;
|
| 70 |
+
width: 0;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
| 74 |
+
float: left;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
| 78 |
+
left: 1px;
|
| 79 |
+
right: auto;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
|
| 83 |
+
background-color: var(--darkened-bg);
|
| 84 |
+
cursor: default;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
| 88 |
+
display: none;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
| 92 |
+
border-color: transparent transparent #888 transparent;
|
| 93 |
+
border-width: 0 4px 5px 4px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.select2-container--admin-autocomplete .select2-selection--multiple {
|
| 97 |
+
background-color: var(--body-bg);
|
| 98 |
+
border: 1px solid var(--border-color);
|
| 99 |
+
border-radius: 4px;
|
| 100 |
+
cursor: text;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
|
| 104 |
+
box-sizing: border-box;
|
| 105 |
+
list-style: none;
|
| 106 |
+
margin: 0;
|
| 107 |
+
padding: 0 10px 5px 5px;
|
| 108 |
+
width: 100%;
|
| 109 |
+
display: flex;
|
| 110 |
+
flex-wrap: wrap;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
|
| 114 |
+
list-style: none;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
|
| 118 |
+
color: var(--body-quiet-color);
|
| 119 |
+
margin-top: 5px;
|
| 120 |
+
float: left;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
|
| 124 |
+
cursor: pointer;
|
| 125 |
+
float: right;
|
| 126 |
+
font-weight: bold;
|
| 127 |
+
margin: 5px;
|
| 128 |
+
position: absolute;
|
| 129 |
+
right: 0;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
|
| 133 |
+
background-color: var(--darkened-bg);
|
| 134 |
+
border: 1px solid var(--border-color);
|
| 135 |
+
border-radius: 4px;
|
| 136 |
+
cursor: default;
|
| 137 |
+
float: left;
|
| 138 |
+
margin-right: 5px;
|
| 139 |
+
margin-top: 5px;
|
| 140 |
+
padding: 0 5px;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
|
| 144 |
+
color: var(--body-quiet-color);
|
| 145 |
+
cursor: pointer;
|
| 146 |
+
display: inline-block;
|
| 147 |
+
font-weight: bold;
|
| 148 |
+
margin-right: 2px;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
|
| 152 |
+
color: var(--body-fg);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
| 156 |
+
float: right;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
| 160 |
+
margin-left: 5px;
|
| 161 |
+
margin-right: auto;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
| 165 |
+
margin-left: 2px;
|
| 166 |
+
margin-right: auto;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
|
| 170 |
+
border: solid var(--body-quiet-color) 1px;
|
| 171 |
+
outline: 0;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
|
| 175 |
+
background-color: var(--darkened-bg);
|
| 176 |
+
cursor: default;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
|
| 180 |
+
display: none;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
|
| 184 |
+
border-top-left-radius: 0;
|
| 185 |
+
border-top-right-radius: 0;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
|
| 189 |
+
border-bottom-left-radius: 0;
|
| 190 |
+
border-bottom-right-radius: 0;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.select2-container--admin-autocomplete .select2-search--dropdown {
|
| 194 |
+
background: var(--darkened-bg);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
|
| 198 |
+
background: var(--body-bg);
|
| 199 |
+
color: var(--body-fg);
|
| 200 |
+
border: 1px solid var(--border-color);
|
| 201 |
+
border-radius: 4px;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
|
| 205 |
+
background: transparent;
|
| 206 |
+
color: var(--body-fg);
|
| 207 |
+
border: none;
|
| 208 |
+
outline: 0;
|
| 209 |
+
box-shadow: none;
|
| 210 |
+
-webkit-appearance: textfield;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
|
| 214 |
+
max-height: 200px;
|
| 215 |
+
overflow-y: auto;
|
| 216 |
+
color: var(--body-fg);
|
| 217 |
+
background: var(--body-bg);
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.select2-container--admin-autocomplete .select2-results__option[role=group] {
|
| 221 |
+
padding: 0;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
|
| 225 |
+
color: var(--body-quiet-color);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
|
| 229 |
+
background-color: var(--selected-bg);
|
| 230 |
+
color: var(--body-fg);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
|
| 234 |
+
padding-left: 1em;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
|
| 238 |
+
padding-left: 0;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
|
| 242 |
+
margin-left: -1em;
|
| 243 |
+
padding-left: 2em;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
| 247 |
+
margin-left: -2em;
|
| 248 |
+
padding-left: 3em;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
| 252 |
+
margin-left: -3em;
|
| 253 |
+
padding-left: 4em;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
| 257 |
+
margin-left: -4em;
|
| 258 |
+
padding-left: 5em;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
| 262 |
+
margin-left: -5em;
|
| 263 |
+
padding-left: 6em;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
|
| 267 |
+
background-color: var(--primary);
|
| 268 |
+
color: var(--primary-fg);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.select2-container--admin-autocomplete .select2-results__group {
|
| 272 |
+
cursor: default;
|
| 273 |
+
display: block;
|
| 274 |
+
padding: 6px;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.errors .select2-selection {
|
| 278 |
+
border: 1px solid var(--error-fg);
|
| 279 |
+
}
|
static/admin/css/base.css
ADDED
|
@@ -0,0 +1,1179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
DJANGO Admin styles
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
/* VARIABLE DEFINITIONS */
|
| 6 |
+
html[data-theme="light"],
|
| 7 |
+
:root {
|
| 8 |
+
--primary: #79aec8;
|
| 9 |
+
--secondary: #417690;
|
| 10 |
+
--accent: #f5dd5d;
|
| 11 |
+
--primary-fg: #fff;
|
| 12 |
+
|
| 13 |
+
--body-fg: #333;
|
| 14 |
+
--body-bg: #fff;
|
| 15 |
+
--body-quiet-color: #666;
|
| 16 |
+
--body-medium-color: #444;
|
| 17 |
+
--body-loud-color: #000;
|
| 18 |
+
|
| 19 |
+
--header-color: #ffc;
|
| 20 |
+
--header-branding-color: var(--accent);
|
| 21 |
+
--header-bg: var(--secondary);
|
| 22 |
+
--header-link-color: var(--primary-fg);
|
| 23 |
+
|
| 24 |
+
--breadcrumbs-fg: #c4dce8;
|
| 25 |
+
--breadcrumbs-link-fg: var(--body-bg);
|
| 26 |
+
--breadcrumbs-bg: #264b5d;
|
| 27 |
+
|
| 28 |
+
--link-fg: #417893;
|
| 29 |
+
--link-hover-color: #036;
|
| 30 |
+
--link-selected-fg: var(--secondary);
|
| 31 |
+
|
| 32 |
+
--hairline-color: #e8e8e8;
|
| 33 |
+
--border-color: #ccc;
|
| 34 |
+
|
| 35 |
+
--error-fg: #ba2121;
|
| 36 |
+
|
| 37 |
+
--message-success-bg: #dfd;
|
| 38 |
+
--message-warning-bg: #ffc;
|
| 39 |
+
--message-error-bg: #ffefef;
|
| 40 |
+
|
| 41 |
+
--darkened-bg: #f8f8f8; /* A bit darker than --body-bg */
|
| 42 |
+
--selected-bg: #e4e4e4; /* E.g. selected table cells */
|
| 43 |
+
--selected-row: #ffc;
|
| 44 |
+
|
| 45 |
+
--button-fg: #fff;
|
| 46 |
+
--button-bg: var(--secondary);
|
| 47 |
+
--button-hover-bg: #205067;
|
| 48 |
+
--default-button-bg: #205067;
|
| 49 |
+
--default-button-hover-bg: var(--secondary);
|
| 50 |
+
--close-button-bg: #747474;
|
| 51 |
+
--close-button-hover-bg: #333;
|
| 52 |
+
--delete-button-bg: #ba2121;
|
| 53 |
+
--delete-button-hover-bg: #a41515;
|
| 54 |
+
|
| 55 |
+
--object-tools-fg: var(--button-fg);
|
| 56 |
+
--object-tools-bg: var(--close-button-bg);
|
| 57 |
+
--object-tools-hover-bg: var(--close-button-hover-bg);
|
| 58 |
+
|
| 59 |
+
--font-family-primary:
|
| 60 |
+
"Segoe UI",
|
| 61 |
+
system-ui,
|
| 62 |
+
Roboto,
|
| 63 |
+
"Helvetica Neue",
|
| 64 |
+
Arial,
|
| 65 |
+
sans-serif,
|
| 66 |
+
"Apple Color Emoji",
|
| 67 |
+
"Segoe UI Emoji",
|
| 68 |
+
"Segoe UI Symbol",
|
| 69 |
+
"Noto Color Emoji";
|
| 70 |
+
--font-family-monospace:
|
| 71 |
+
ui-monospace,
|
| 72 |
+
Menlo,
|
| 73 |
+
Monaco,
|
| 74 |
+
"Cascadia Mono",
|
| 75 |
+
"Segoe UI Mono",
|
| 76 |
+
"Roboto Mono",
|
| 77 |
+
"Oxygen Mono",
|
| 78 |
+
"Ubuntu Monospace",
|
| 79 |
+
"Source Code Pro",
|
| 80 |
+
"Fira Mono",
|
| 81 |
+
"Droid Sans Mono",
|
| 82 |
+
"Courier New",
|
| 83 |
+
monospace,
|
| 84 |
+
"Apple Color Emoji",
|
| 85 |
+
"Segoe UI Emoji",
|
| 86 |
+
"Segoe UI Symbol",
|
| 87 |
+
"Noto Color Emoji";
|
| 88 |
+
|
| 89 |
+
color-scheme: light;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
html, body {
|
| 93 |
+
height: 100%;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
body {
|
| 97 |
+
margin: 0;
|
| 98 |
+
padding: 0;
|
| 99 |
+
font-size: 0.875rem;
|
| 100 |
+
font-family: var(--font-family-primary);
|
| 101 |
+
color: var(--body-fg);
|
| 102 |
+
background: var(--body-bg);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/* LINKS */
|
| 106 |
+
|
| 107 |
+
a:link, a:visited {
|
| 108 |
+
color: var(--link-fg);
|
| 109 |
+
text-decoration: none;
|
| 110 |
+
transition: color 0.15s, background 0.15s;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
a:focus, a:hover {
|
| 114 |
+
color: var(--link-hover-color);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
a:focus {
|
| 118 |
+
text-decoration: underline;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
a img {
|
| 122 |
+
border: none;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
a.section:link, a.section:visited {
|
| 126 |
+
color: var(--header-link-color);
|
| 127 |
+
text-decoration: none;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
a.section:focus, a.section:hover {
|
| 131 |
+
text-decoration: underline;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
/* GLOBAL DEFAULTS */
|
| 135 |
+
|
| 136 |
+
p, ol, ul, dl {
|
| 137 |
+
margin: .2em 0 .8em 0;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
p {
|
| 141 |
+
padding: 0;
|
| 142 |
+
line-height: 140%;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
h1,h2,h3,h4,h5 {
|
| 146 |
+
font-weight: bold;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
h1 {
|
| 150 |
+
margin: 0 0 20px;
|
| 151 |
+
font-weight: 300;
|
| 152 |
+
font-size: 1.25rem;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
h2 {
|
| 156 |
+
font-size: 1rem;
|
| 157 |
+
margin: 1em 0 .5em 0;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
h2.subhead {
|
| 161 |
+
font-weight: normal;
|
| 162 |
+
margin-top: 0;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
h3 {
|
| 166 |
+
font-size: 0.875rem;
|
| 167 |
+
margin: .8em 0 .3em 0;
|
| 168 |
+
color: var(--body-medium-color);
|
| 169 |
+
font-weight: bold;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
h4 {
|
| 173 |
+
font-size: 0.75rem;
|
| 174 |
+
margin: 1em 0 .8em 0;
|
| 175 |
+
padding-bottom: 3px;
|
| 176 |
+
color: var(--body-medium-color);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
h5 {
|
| 180 |
+
font-size: 0.625rem;
|
| 181 |
+
margin: 1.5em 0 .5em 0;
|
| 182 |
+
color: var(--body-quiet-color);
|
| 183 |
+
text-transform: uppercase;
|
| 184 |
+
letter-spacing: 1px;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
ul > li {
|
| 188 |
+
list-style-type: square;
|
| 189 |
+
padding: 1px 0;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
li ul {
|
| 193 |
+
margin-bottom: 0;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
li, dt, dd {
|
| 197 |
+
font-size: 0.8125rem;
|
| 198 |
+
line-height: 1.25rem;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
dt {
|
| 202 |
+
font-weight: bold;
|
| 203 |
+
margin-top: 4px;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
dd {
|
| 207 |
+
margin-left: 0;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
form {
|
| 211 |
+
margin: 0;
|
| 212 |
+
padding: 0;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
fieldset {
|
| 216 |
+
margin: 0;
|
| 217 |
+
min-width: 0;
|
| 218 |
+
padding: 0;
|
| 219 |
+
border: none;
|
| 220 |
+
border-top: 1px solid var(--hairline-color);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
details summary {
|
| 224 |
+
cursor: pointer;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
blockquote {
|
| 228 |
+
font-size: 0.6875rem;
|
| 229 |
+
color: #777;
|
| 230 |
+
margin-left: 2px;
|
| 231 |
+
padding-left: 10px;
|
| 232 |
+
border-left: 5px solid #ddd;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
code, pre {
|
| 236 |
+
font-family: var(--font-family-monospace);
|
| 237 |
+
color: var(--body-quiet-color);
|
| 238 |
+
font-size: 0.75rem;
|
| 239 |
+
overflow-x: auto;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
pre.literal-block {
|
| 243 |
+
margin: 10px;
|
| 244 |
+
background: var(--darkened-bg);
|
| 245 |
+
padding: 6px 8px;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
code strong {
|
| 249 |
+
color: #930;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
hr {
|
| 253 |
+
clear: both;
|
| 254 |
+
color: var(--hairline-color);
|
| 255 |
+
background-color: var(--hairline-color);
|
| 256 |
+
height: 1px;
|
| 257 |
+
border: none;
|
| 258 |
+
margin: 0;
|
| 259 |
+
padding: 0;
|
| 260 |
+
line-height: 1px;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
/* TEXT STYLES & MODIFIERS */
|
| 264 |
+
|
| 265 |
+
.small {
|
| 266 |
+
font-size: 0.6875rem;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.mini {
|
| 270 |
+
font-size: 0.625rem;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.help, p.help, form p.help, div.help, form div.help, div.help li {
|
| 274 |
+
font-size: 0.6875rem;
|
| 275 |
+
color: var(--body-quiet-color);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
div.help ul {
|
| 279 |
+
margin-bottom: 0;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.help-tooltip {
|
| 283 |
+
cursor: help;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
p img, h1 img, h2 img, h3 img, h4 img, td img {
|
| 287 |
+
vertical-align: middle;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.quiet, a.quiet:link, a.quiet:visited {
|
| 291 |
+
color: var(--body-quiet-color);
|
| 292 |
+
font-weight: normal;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.clear {
|
| 296 |
+
clear: both;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.nowrap {
|
| 300 |
+
white-space: nowrap;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.hidden {
|
| 304 |
+
display: none !important;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
/* TABLES */
|
| 308 |
+
|
| 309 |
+
table {
|
| 310 |
+
border-collapse: collapse;
|
| 311 |
+
border-color: var(--border-color);
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
td, th {
|
| 315 |
+
font-size: 0.8125rem;
|
| 316 |
+
line-height: 1rem;
|
| 317 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 318 |
+
vertical-align: top;
|
| 319 |
+
padding: 8px;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
th {
|
| 323 |
+
font-weight: 500;
|
| 324 |
+
text-align: left;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
thead th,
|
| 328 |
+
tfoot td {
|
| 329 |
+
color: var(--body-quiet-color);
|
| 330 |
+
padding: 5px 10px;
|
| 331 |
+
font-size: 0.6875rem;
|
| 332 |
+
background: var(--body-bg);
|
| 333 |
+
border: none;
|
| 334 |
+
border-top: 1px solid var(--hairline-color);
|
| 335 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
tfoot td {
|
| 339 |
+
border-bottom: none;
|
| 340 |
+
border-top: 1px solid var(--hairline-color);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
thead th.required {
|
| 344 |
+
font-weight: bold;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
tr.alt {
|
| 348 |
+
background: var(--darkened-bg);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
tr:nth-child(odd), .row-form-errors {
|
| 352 |
+
background: var(--body-bg);
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
tr:nth-child(even),
|
| 356 |
+
tr:nth-child(even) .errorlist,
|
| 357 |
+
tr:nth-child(odd) + .row-form-errors,
|
| 358 |
+
tr:nth-child(odd) + .row-form-errors .errorlist {
|
| 359 |
+
background: var(--darkened-bg);
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/* SORTABLE TABLES */
|
| 363 |
+
|
| 364 |
+
thead th {
|
| 365 |
+
padding: 5px 10px;
|
| 366 |
+
line-height: normal;
|
| 367 |
+
text-transform: uppercase;
|
| 368 |
+
background: var(--darkened-bg);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
thead th a:link, thead th a:visited {
|
| 372 |
+
color: var(--body-quiet-color);
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
thead th.sorted {
|
| 376 |
+
background: var(--selected-bg);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
thead th.sorted .text {
|
| 380 |
+
padding-right: 42px;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
table thead th .text span {
|
| 384 |
+
padding: 8px 10px;
|
| 385 |
+
display: block;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
table thead th .text a {
|
| 389 |
+
display: block;
|
| 390 |
+
cursor: pointer;
|
| 391 |
+
padding: 8px 10px;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
table thead th .text a:focus, table thead th .text a:hover {
|
| 395 |
+
background: var(--selected-bg);
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
thead th.sorted a.sortremove {
|
| 399 |
+
visibility: hidden;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
table thead th.sorted:hover a.sortremove {
|
| 403 |
+
visibility: visible;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
table thead th.sorted .sortoptions {
|
| 407 |
+
display: block;
|
| 408 |
+
padding: 9px 5px 0 5px;
|
| 409 |
+
float: right;
|
| 410 |
+
text-align: right;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
table thead th.sorted .sortpriority {
|
| 414 |
+
font-size: .8em;
|
| 415 |
+
min-width: 12px;
|
| 416 |
+
text-align: center;
|
| 417 |
+
vertical-align: 3px;
|
| 418 |
+
margin-left: 2px;
|
| 419 |
+
margin-right: 2px;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
table thead th.sorted .sortoptions a {
|
| 423 |
+
position: relative;
|
| 424 |
+
width: 14px;
|
| 425 |
+
height: 14px;
|
| 426 |
+
display: inline-block;
|
| 427 |
+
background: url(../img/sorting-icons.svg) 0 0 no-repeat;
|
| 428 |
+
background-size: 14px auto;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
table thead th.sorted .sortoptions a.sortremove {
|
| 432 |
+
background-position: 0 0;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
table thead th.sorted .sortoptions a.sortremove:after {
|
| 436 |
+
content: '\\';
|
| 437 |
+
position: absolute;
|
| 438 |
+
top: -6px;
|
| 439 |
+
left: 3px;
|
| 440 |
+
font-weight: 200;
|
| 441 |
+
font-size: 1.125rem;
|
| 442 |
+
color: var(--body-quiet-color);
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
table thead th.sorted .sortoptions a.sortremove:focus:after,
|
| 446 |
+
table thead th.sorted .sortoptions a.sortremove:hover:after {
|
| 447 |
+
color: var(--link-fg);
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
table thead th.sorted .sortoptions a.sortremove:focus,
|
| 451 |
+
table thead th.sorted .sortoptions a.sortremove:hover {
|
| 452 |
+
background-position: 0 -14px;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
table thead th.sorted .sortoptions a.ascending {
|
| 456 |
+
background-position: 0 -28px;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
table thead th.sorted .sortoptions a.ascending:focus,
|
| 460 |
+
table thead th.sorted .sortoptions a.ascending:hover {
|
| 461 |
+
background-position: 0 -42px;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
table thead th.sorted .sortoptions a.descending {
|
| 465 |
+
top: 1px;
|
| 466 |
+
background-position: 0 -56px;
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
table thead th.sorted .sortoptions a.descending:focus,
|
| 470 |
+
table thead th.sorted .sortoptions a.descending:hover {
|
| 471 |
+
background-position: 0 -70px;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
/* FORM DEFAULTS */
|
| 475 |
+
|
| 476 |
+
input, textarea, select, .form-row p, form .button {
|
| 477 |
+
margin: 2px 0;
|
| 478 |
+
padding: 2px 3px;
|
| 479 |
+
vertical-align: middle;
|
| 480 |
+
font-family: var(--font-family-primary);
|
| 481 |
+
font-weight: normal;
|
| 482 |
+
font-size: 0.8125rem;
|
| 483 |
+
}
|
| 484 |
+
.form-row div.help {
|
| 485 |
+
padding: 2px 3px;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
textarea {
|
| 489 |
+
vertical-align: top;
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
/*
|
| 493 |
+
Minifiers remove the default (text) "type" attribute from "input" HTML tags.
|
| 494 |
+
Add input:not([type]) to make the CSS stylesheet work the same.
|
| 495 |
+
*/
|
| 496 |
+
input:not([type]), input[type=text], input[type=password], input[type=email],
|
| 497 |
+
input[type=url], input[type=number], input[type=tel], textarea, select,
|
| 498 |
+
.vTextField {
|
| 499 |
+
border: 1px solid var(--border-color);
|
| 500 |
+
border-radius: 4px;
|
| 501 |
+
padding: 5px 6px;
|
| 502 |
+
margin-top: 0;
|
| 503 |
+
color: var(--body-fg);
|
| 504 |
+
background-color: var(--body-bg);
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
/*
|
| 508 |
+
Minifiers remove the default (text) "type" attribute from "input" HTML tags.
|
| 509 |
+
Add input:not([type]) to make the CSS stylesheet work the same.
|
| 510 |
+
*/
|
| 511 |
+
input:not([type]):focus, input[type=text]:focus, input[type=password]:focus,
|
| 512 |
+
input[type=email]:focus, input[type=url]:focus, input[type=number]:focus,
|
| 513 |
+
input[type=tel]:focus, textarea:focus, select:focus, .vTextField:focus {
|
| 514 |
+
border-color: var(--body-quiet-color);
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
select {
|
| 518 |
+
height: 1.875rem;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
select[multiple] {
|
| 522 |
+
/* Allow HTML size attribute to override the height in the rule above. */
|
| 523 |
+
height: auto;
|
| 524 |
+
min-height: 150px;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
/* FORM BUTTONS */
|
| 528 |
+
|
| 529 |
+
.button, input[type=submit], input[type=button], .submit-row input, a.button {
|
| 530 |
+
background: var(--button-bg);
|
| 531 |
+
padding: 10px 15px;
|
| 532 |
+
border: none;
|
| 533 |
+
border-radius: 4px;
|
| 534 |
+
color: var(--button-fg);
|
| 535 |
+
cursor: pointer;
|
| 536 |
+
transition: background 0.15s;
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
a.button {
|
| 540 |
+
padding: 4px 5px;
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
.button:active, input[type=submit]:active, input[type=button]:active,
|
| 544 |
+
.button:focus, input[type=submit]:focus, input[type=button]:focus,
|
| 545 |
+
.button:hover, input[type=submit]:hover, input[type=button]:hover {
|
| 546 |
+
background: var(--button-hover-bg);
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
|
| 550 |
+
opacity: 0.4;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
.button.default, input[type=submit].default, .submit-row input.default {
|
| 554 |
+
border: none;
|
| 555 |
+
font-weight: 400;
|
| 556 |
+
background: var(--default-button-bg);
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
.button.default:active, input[type=submit].default:active,
|
| 560 |
+
.button.default:focus, input[type=submit].default:focus,
|
| 561 |
+
.button.default:hover, input[type=submit].default:hover {
|
| 562 |
+
background: var(--default-button-hover-bg);
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
.button[disabled].default,
|
| 566 |
+
input[type=submit][disabled].default,
|
| 567 |
+
input[type=button][disabled].default {
|
| 568 |
+
opacity: 0.4;
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
/* MODULES */
|
| 573 |
+
|
| 574 |
+
.module {
|
| 575 |
+
border: none;
|
| 576 |
+
margin-bottom: 30px;
|
| 577 |
+
background: var(--body-bg);
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
|
| 581 |
+
padding-left: 10px;
|
| 582 |
+
padding-right: 10px;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
.module blockquote {
|
| 586 |
+
margin-left: 12px;
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
.module ul, .module ol {
|
| 590 |
+
margin-left: 1.5em;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.module h3 {
|
| 594 |
+
margin-top: .6em;
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
.module h2, .module caption, .inline-group h2 {
|
| 598 |
+
margin: 0;
|
| 599 |
+
padding: 8px;
|
| 600 |
+
font-weight: 400;
|
| 601 |
+
font-size: 0.8125rem;
|
| 602 |
+
text-align: left;
|
| 603 |
+
background: var(--header-bg);
|
| 604 |
+
color: var(--header-link-color);
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
.module caption,
|
| 608 |
+
.inline-group h2 {
|
| 609 |
+
font-size: 0.75rem;
|
| 610 |
+
letter-spacing: 0.5px;
|
| 611 |
+
text-transform: uppercase;
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
.module table {
|
| 615 |
+
border-collapse: collapse;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
/* MESSAGES & ERRORS */
|
| 619 |
+
|
| 620 |
+
ul.messagelist {
|
| 621 |
+
padding: 0;
|
| 622 |
+
margin: 0;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
ul.messagelist li {
|
| 626 |
+
display: block;
|
| 627 |
+
font-weight: 400;
|
| 628 |
+
font-size: 0.8125rem;
|
| 629 |
+
padding: 10px 10px 10px 65px;
|
| 630 |
+
margin: 0 0 10px 0;
|
| 631 |
+
background: var(--message-success-bg) url(../img/icon-yes.svg) 40px 12px no-repeat;
|
| 632 |
+
background-size: 16px auto;
|
| 633 |
+
color: var(--body-fg);
|
| 634 |
+
word-break: break-word;
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
ul.messagelist li.warning {
|
| 638 |
+
background: var(--message-warning-bg) url(../img/icon-alert.svg) 40px 14px no-repeat;
|
| 639 |
+
background-size: 14px auto;
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
ul.messagelist li.error {
|
| 643 |
+
background: var(--message-error-bg) url(../img/icon-no.svg) 40px 12px no-repeat;
|
| 644 |
+
background-size: 16px auto;
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
.errornote {
|
| 648 |
+
font-size: 0.875rem;
|
| 649 |
+
font-weight: 700;
|
| 650 |
+
display: block;
|
| 651 |
+
padding: 10px 12px;
|
| 652 |
+
margin: 0 0 10px 0;
|
| 653 |
+
color: var(--error-fg);
|
| 654 |
+
border: 1px solid var(--error-fg);
|
| 655 |
+
border-radius: 4px;
|
| 656 |
+
background-color: var(--body-bg);
|
| 657 |
+
background-position: 5px 12px;
|
| 658 |
+
overflow-wrap: break-word;
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
ul.errorlist {
|
| 662 |
+
margin: 0 0 4px;
|
| 663 |
+
padding: 0;
|
| 664 |
+
color: var(--error-fg);
|
| 665 |
+
background: var(--body-bg);
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
ul.errorlist li {
|
| 669 |
+
font-size: 0.8125rem;
|
| 670 |
+
display: block;
|
| 671 |
+
margin-bottom: 4px;
|
| 672 |
+
overflow-wrap: break-word;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
ul.errorlist li:first-child {
|
| 676 |
+
margin-top: 0;
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
ul.errorlist li a {
|
| 680 |
+
color: inherit;
|
| 681 |
+
text-decoration: underline;
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
td ul.errorlist {
|
| 685 |
+
margin: 0;
|
| 686 |
+
padding: 0;
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
td ul.errorlist li {
|
| 690 |
+
margin: 0;
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
.form-row.errors {
|
| 694 |
+
margin: 0;
|
| 695 |
+
border: none;
|
| 696 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 697 |
+
background: none;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
.form-row.errors ul.errorlist li {
|
| 701 |
+
padding-left: 0;
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.errors input, .errors select, .errors textarea,
|
| 705 |
+
td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
|
| 706 |
+
border: 1px solid var(--error-fg);
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
.description {
|
| 710 |
+
font-size: 0.75rem;
|
| 711 |
+
padding: 5px 0 0 12px;
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
/* BREADCRUMBS */
|
| 715 |
+
|
| 716 |
+
div.breadcrumbs {
|
| 717 |
+
background: var(--breadcrumbs-bg);
|
| 718 |
+
padding: 10px 40px;
|
| 719 |
+
border: none;
|
| 720 |
+
color: var(--breadcrumbs-fg);
|
| 721 |
+
text-align: left;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
div.breadcrumbs a {
|
| 725 |
+
color: var(--breadcrumbs-link-fg);
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
div.breadcrumbs a:focus, div.breadcrumbs a:hover {
|
| 729 |
+
color: var(--breadcrumbs-fg);
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
/* ACTION ICONS */
|
| 733 |
+
|
| 734 |
+
.viewlink, .inlineviewlink {
|
| 735 |
+
padding-left: 16px;
|
| 736 |
+
background: url(../img/icon-viewlink.svg) 0 1px no-repeat;
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
.hidelink {
|
| 740 |
+
padding-left: 16px;
|
| 741 |
+
background: url(../img/icon-hidelink.svg) 0 1px no-repeat;
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
.addlink {
|
| 745 |
+
padding-left: 16px;
|
| 746 |
+
background: url(../img/icon-addlink.svg) 0 1px no-repeat;
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
.changelink, .inlinechangelink {
|
| 750 |
+
padding-left: 16px;
|
| 751 |
+
background: url(../img/icon-changelink.svg) 0 1px no-repeat;
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
.deletelink {
|
| 755 |
+
padding-left: 16px;
|
| 756 |
+
background: url(../img/icon-deletelink.svg) 0 1px no-repeat;
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
a.deletelink:link, a.deletelink:visited {
|
| 760 |
+
color: #CC3434; /* XXX Probably unused? */
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
a.deletelink:focus, a.deletelink:hover {
|
| 764 |
+
color: #993333; /* XXX Probably unused? */
|
| 765 |
+
text-decoration: none;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
/* OBJECT TOOLS */
|
| 769 |
+
|
| 770 |
+
.object-tools {
|
| 771 |
+
font-size: 0.625rem;
|
| 772 |
+
font-weight: bold;
|
| 773 |
+
padding-left: 0;
|
| 774 |
+
float: right;
|
| 775 |
+
position: relative;
|
| 776 |
+
margin-top: -48px;
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
.object-tools li {
|
| 780 |
+
display: block;
|
| 781 |
+
float: left;
|
| 782 |
+
margin-left: 5px;
|
| 783 |
+
height: 1rem;
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
.object-tools a {
|
| 787 |
+
border-radius: 15px;
|
| 788 |
+
}
|
| 789 |
+
|
| 790 |
+
.object-tools a:link, .object-tools a:visited {
|
| 791 |
+
display: block;
|
| 792 |
+
float: left;
|
| 793 |
+
padding: 3px 12px;
|
| 794 |
+
background: var(--object-tools-bg);
|
| 795 |
+
color: var(--object-tools-fg);
|
| 796 |
+
font-weight: 400;
|
| 797 |
+
font-size: 0.6875rem;
|
| 798 |
+
text-transform: uppercase;
|
| 799 |
+
letter-spacing: 0.5px;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
.object-tools a:focus, .object-tools a:hover {
|
| 803 |
+
background-color: var(--object-tools-hover-bg);
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
.object-tools a:focus{
|
| 807 |
+
text-decoration: none;
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
.object-tools a.viewsitelink, .object-tools a.addlink {
|
| 811 |
+
background-repeat: no-repeat;
|
| 812 |
+
background-position: right 7px center;
|
| 813 |
+
padding-right: 26px;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
.object-tools a.viewsitelink {
|
| 817 |
+
background-image: url(../img/tooltag-arrowright.svg);
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
.object-tools a.addlink {
|
| 821 |
+
background-image: url(../img/tooltag-add.svg);
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
/* OBJECT HISTORY */
|
| 825 |
+
|
| 826 |
+
#change-history table {
|
| 827 |
+
width: 100%;
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
#change-history table tbody th {
|
| 831 |
+
width: 16em;
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
#change-history .paginator {
|
| 835 |
+
color: var(--body-quiet-color);
|
| 836 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 837 |
+
background: var(--body-bg);
|
| 838 |
+
overflow: hidden;
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
/* PAGE STRUCTURE */
|
| 842 |
+
|
| 843 |
+
#container {
|
| 844 |
+
position: relative;
|
| 845 |
+
width: 100%;
|
| 846 |
+
min-width: 980px;
|
| 847 |
+
padding: 0;
|
| 848 |
+
display: flex;
|
| 849 |
+
flex-direction: column;
|
| 850 |
+
height: 100%;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
#container > .main {
|
| 854 |
+
display: flex;
|
| 855 |
+
flex: 1 0 auto;
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
.main > .content {
|
| 859 |
+
flex: 1 0;
|
| 860 |
+
max-width: 100%;
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
.skip-to-content-link {
|
| 864 |
+
position: absolute;
|
| 865 |
+
top: -999px;
|
| 866 |
+
margin: 5px;
|
| 867 |
+
padding: 5px;
|
| 868 |
+
background: var(--body-bg);
|
| 869 |
+
z-index: 1;
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
.skip-to-content-link:focus {
|
| 873 |
+
left: 0px;
|
| 874 |
+
top: 0px;
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
#content {
|
| 878 |
+
padding: 20px 40px;
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
.dashboard #content {
|
| 882 |
+
width: 600px;
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
#content-main {
|
| 886 |
+
float: left;
|
| 887 |
+
width: 100%;
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
#content-related {
|
| 891 |
+
float: right;
|
| 892 |
+
width: 260px;
|
| 893 |
+
position: relative;
|
| 894 |
+
margin-right: -300px;
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
@media (forced-colors: active) {
|
| 898 |
+
#content-related {
|
| 899 |
+
border: 1px solid;
|
| 900 |
+
}
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
/* COLUMN TYPES */
|
| 904 |
+
|
| 905 |
+
.colMS {
|
| 906 |
+
margin-right: 300px;
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
.colSM {
|
| 910 |
+
margin-left: 300px;
|
| 911 |
+
}
|
| 912 |
+
|
| 913 |
+
.colSM #content-related {
|
| 914 |
+
float: left;
|
| 915 |
+
margin-right: 0;
|
| 916 |
+
margin-left: -300px;
|
| 917 |
+
}
|
| 918 |
+
|
| 919 |
+
.colSM #content-main {
|
| 920 |
+
float: right;
|
| 921 |
+
}
|
| 922 |
+
|
| 923 |
+
.popup .colM {
|
| 924 |
+
width: auto;
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
/* HEADER */
|
| 928 |
+
|
| 929 |
+
#header {
|
| 930 |
+
width: auto;
|
| 931 |
+
height: auto;
|
| 932 |
+
display: flex;
|
| 933 |
+
justify-content: space-between;
|
| 934 |
+
align-items: center;
|
| 935 |
+
padding: 10px 40px;
|
| 936 |
+
background: var(--header-bg);
|
| 937 |
+
color: var(--header-color);
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
#header a:link, #header a:visited, #logout-form button {
|
| 941 |
+
color: var(--header-link-color);
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
#header a:focus , #header a:hover {
|
| 945 |
+
text-decoration: underline;
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
@media (forced-colors: active) {
|
| 949 |
+
#header {
|
| 950 |
+
border-bottom: 1px solid;
|
| 951 |
+
}
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
#branding {
|
| 955 |
+
display: flex;
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
#site-name {
|
| 959 |
+
padding: 0;
|
| 960 |
+
margin: 0;
|
| 961 |
+
margin-inline-end: 20px;
|
| 962 |
+
font-weight: 300;
|
| 963 |
+
font-size: 1.5rem;
|
| 964 |
+
color: var(--header-branding-color);
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
#site-name a:link, #site-name a:visited {
|
| 968 |
+
color: var(--accent);
|
| 969 |
+
}
|
| 970 |
+
|
| 971 |
+
#branding h2 {
|
| 972 |
+
padding: 0 10px;
|
| 973 |
+
font-size: 0.875rem;
|
| 974 |
+
margin: -8px 0 8px 0;
|
| 975 |
+
font-weight: normal;
|
| 976 |
+
color: var(--header-color);
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
#branding a:hover {
|
| 980 |
+
text-decoration: none;
|
| 981 |
+
}
|
| 982 |
+
|
| 983 |
+
#logout-form {
|
| 984 |
+
display: inline;
|
| 985 |
+
}
|
| 986 |
+
|
| 987 |
+
#logout-form button {
|
| 988 |
+
background: none;
|
| 989 |
+
border: 0;
|
| 990 |
+
cursor: pointer;
|
| 991 |
+
font-family: var(--font-family-primary);
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
#user-tools {
|
| 995 |
+
float: right;
|
| 996 |
+
margin: 0 0 0 20px;
|
| 997 |
+
text-align: right;
|
| 998 |
+
}
|
| 999 |
+
|
| 1000 |
+
#user-tools, #logout-form button{
|
| 1001 |
+
padding: 0;
|
| 1002 |
+
font-weight: 300;
|
| 1003 |
+
font-size: 0.6875rem;
|
| 1004 |
+
letter-spacing: 0.5px;
|
| 1005 |
+
text-transform: uppercase;
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
#user-tools a, #logout-form button {
|
| 1009 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
#user-tools a:focus, #user-tools a:hover,
|
| 1013 |
+
#logout-form button:active, #logout-form button:hover {
|
| 1014 |
+
text-decoration: none;
|
| 1015 |
+
border-bottom: 0;
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
#logout-form button:active, #logout-form button:hover {
|
| 1019 |
+
margin-bottom: 1px;
|
| 1020 |
+
}
|
| 1021 |
+
|
| 1022 |
+
/* SIDEBAR */
|
| 1023 |
+
|
| 1024 |
+
#content-related {
|
| 1025 |
+
background: var(--darkened-bg);
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
#content-related .module {
|
| 1029 |
+
background: none;
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
#content-related h3 {
|
| 1033 |
+
color: var(--body-quiet-color);
|
| 1034 |
+
padding: 0 16px;
|
| 1035 |
+
margin: 0 0 16px;
|
| 1036 |
+
}
|
| 1037 |
+
|
| 1038 |
+
#content-related h4 {
|
| 1039 |
+
font-size: 0.8125rem;
|
| 1040 |
+
}
|
| 1041 |
+
|
| 1042 |
+
#content-related p {
|
| 1043 |
+
padding-left: 16px;
|
| 1044 |
+
padding-right: 16px;
|
| 1045 |
+
}
|
| 1046 |
+
|
| 1047 |
+
#content-related .actionlist {
|
| 1048 |
+
padding: 0;
|
| 1049 |
+
margin: 16px;
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
#content-related .actionlist li {
|
| 1053 |
+
line-height: 1.2;
|
| 1054 |
+
margin-bottom: 10px;
|
| 1055 |
+
padding-left: 18px;
|
| 1056 |
+
}
|
| 1057 |
+
|
| 1058 |
+
#content-related .module h2 {
|
| 1059 |
+
background: none;
|
| 1060 |
+
padding: 16px;
|
| 1061 |
+
margin-bottom: 16px;
|
| 1062 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 1063 |
+
font-size: 1.125rem;
|
| 1064 |
+
color: var(--body-fg);
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
.delete-confirmation form input[type="submit"] {
|
| 1068 |
+
background: var(--delete-button-bg);
|
| 1069 |
+
border-radius: 4px;
|
| 1070 |
+
padding: 10px 15px;
|
| 1071 |
+
color: var(--button-fg);
|
| 1072 |
+
}
|
| 1073 |
+
|
| 1074 |
+
.delete-confirmation form input[type="submit"]:active,
|
| 1075 |
+
.delete-confirmation form input[type="submit"]:focus,
|
| 1076 |
+
.delete-confirmation form input[type="submit"]:hover {
|
| 1077 |
+
background: var(--delete-button-hover-bg);
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
.delete-confirmation form .cancel-link {
|
| 1081 |
+
display: inline-block;
|
| 1082 |
+
vertical-align: middle;
|
| 1083 |
+
height: 0.9375rem;
|
| 1084 |
+
line-height: 0.9375rem;
|
| 1085 |
+
border-radius: 4px;
|
| 1086 |
+
padding: 10px 15px;
|
| 1087 |
+
color: var(--button-fg);
|
| 1088 |
+
background: var(--close-button-bg);
|
| 1089 |
+
margin: 0 0 0 10px;
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
.delete-confirmation form .cancel-link:active,
|
| 1093 |
+
.delete-confirmation form .cancel-link:focus,
|
| 1094 |
+
.delete-confirmation form .cancel-link:hover {
|
| 1095 |
+
background: var(--close-button-hover-bg);
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
/* POPUP */
|
| 1099 |
+
.popup #content {
|
| 1100 |
+
padding: 20px;
|
| 1101 |
+
}
|
| 1102 |
+
|
| 1103 |
+
.popup #container {
|
| 1104 |
+
min-width: 0;
|
| 1105 |
+
}
|
| 1106 |
+
|
| 1107 |
+
.popup #header {
|
| 1108 |
+
padding: 10px 20px;
|
| 1109 |
+
}
|
| 1110 |
+
|
| 1111 |
+
/* PAGINATOR */
|
| 1112 |
+
|
| 1113 |
+
.paginator {
|
| 1114 |
+
display: flex;
|
| 1115 |
+
align-items: center;
|
| 1116 |
+
gap: 4px;
|
| 1117 |
+
font-size: 0.8125rem;
|
| 1118 |
+
padding-top: 10px;
|
| 1119 |
+
padding-bottom: 10px;
|
| 1120 |
+
line-height: 22px;
|
| 1121 |
+
margin: 0;
|
| 1122 |
+
border-top: 1px solid var(--hairline-color);
|
| 1123 |
+
width: 100%;
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
.paginator a:link, .paginator a:visited {
|
| 1127 |
+
padding: 2px 6px;
|
| 1128 |
+
background: var(--button-bg);
|
| 1129 |
+
text-decoration: none;
|
| 1130 |
+
color: var(--button-fg);
|
| 1131 |
+
}
|
| 1132 |
+
|
| 1133 |
+
.paginator a.showall {
|
| 1134 |
+
border: none;
|
| 1135 |
+
background: none;
|
| 1136 |
+
color: var(--link-fg);
|
| 1137 |
+
}
|
| 1138 |
+
|
| 1139 |
+
.paginator a.showall:focus, .paginator a.showall:hover {
|
| 1140 |
+
background: none;
|
| 1141 |
+
color: var(--link-hover-color);
|
| 1142 |
+
}
|
| 1143 |
+
|
| 1144 |
+
.paginator .end {
|
| 1145 |
+
margin-right: 6px;
|
| 1146 |
+
}
|
| 1147 |
+
|
| 1148 |
+
.paginator .this-page {
|
| 1149 |
+
padding: 2px 6px;
|
| 1150 |
+
font-weight: bold;
|
| 1151 |
+
font-size: 0.8125rem;
|
| 1152 |
+
vertical-align: top;
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
.paginator a:focus, .paginator a:hover {
|
| 1156 |
+
color: white;
|
| 1157 |
+
background: var(--link-hover-color);
|
| 1158 |
+
}
|
| 1159 |
+
|
| 1160 |
+
.paginator input {
|
| 1161 |
+
margin-left: auto;
|
| 1162 |
+
}
|
| 1163 |
+
|
| 1164 |
+
.base-svgs {
|
| 1165 |
+
display: none;
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
+
.visually-hidden {
|
| 1169 |
+
position: absolute;
|
| 1170 |
+
width: 1px;
|
| 1171 |
+
height: 1px;
|
| 1172 |
+
padding: 0;
|
| 1173 |
+
overflow: hidden;
|
| 1174 |
+
clip: rect(0,0,0,0);
|
| 1175 |
+
white-space: nowrap;
|
| 1176 |
+
border: 0;
|
| 1177 |
+
color: var(--body-fg);
|
| 1178 |
+
background-color: var(--body-bg);
|
| 1179 |
+
}
|
static/admin/css/changelists.css
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* CHANGELISTS */
|
| 2 |
+
|
| 3 |
+
#changelist {
|
| 4 |
+
display: flex;
|
| 5 |
+
align-items: flex-start;
|
| 6 |
+
justify-content: space-between;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
#changelist .changelist-form-container {
|
| 10 |
+
flex: 1 1 auto;
|
| 11 |
+
min-width: 0;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
#changelist table {
|
| 15 |
+
width: 100%;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.change-list .hiddenfields { display:none; }
|
| 19 |
+
|
| 20 |
+
.change-list .filtered table {
|
| 21 |
+
border-right: none;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.change-list .filtered {
|
| 25 |
+
min-height: 400px;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.change-list .filtered .results, .change-list .filtered .paginator,
|
| 29 |
+
.filtered #toolbar, .filtered div.xfull {
|
| 30 |
+
width: auto;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.change-list .filtered table tbody th {
|
| 34 |
+
padding-right: 1em;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
#changelist-form .results {
|
| 38 |
+
overflow-x: auto;
|
| 39 |
+
width: 100%;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
#changelist .toplinks {
|
| 43 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
#changelist .paginator {
|
| 47 |
+
color: var(--body-quiet-color);
|
| 48 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 49 |
+
background: var(--body-bg);
|
| 50 |
+
overflow: hidden;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/* CHANGELIST TABLES */
|
| 54 |
+
|
| 55 |
+
#changelist table thead th {
|
| 56 |
+
padding: 0;
|
| 57 |
+
white-space: nowrap;
|
| 58 |
+
vertical-align: middle;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
#changelist table thead th.action-checkbox-column {
|
| 62 |
+
width: 1.5em;
|
| 63 |
+
text-align: center;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
#changelist table tbody td.action-checkbox {
|
| 67 |
+
text-align: center;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#changelist table tfoot {
|
| 71 |
+
color: var(--body-quiet-color);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/* TOOLBAR */
|
| 75 |
+
|
| 76 |
+
#toolbar {
|
| 77 |
+
padding: 8px 10px;
|
| 78 |
+
margin-bottom: 15px;
|
| 79 |
+
border-top: 1px solid var(--hairline-color);
|
| 80 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 81 |
+
background: var(--darkened-bg);
|
| 82 |
+
color: var(--body-quiet-color);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
#toolbar form input {
|
| 86 |
+
border-radius: 4px;
|
| 87 |
+
font-size: 0.875rem;
|
| 88 |
+
padding: 5px;
|
| 89 |
+
color: var(--body-fg);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
#toolbar #searchbar {
|
| 93 |
+
height: 1.1875rem;
|
| 94 |
+
border: 1px solid var(--border-color);
|
| 95 |
+
padding: 2px 5px;
|
| 96 |
+
margin: 0;
|
| 97 |
+
vertical-align: top;
|
| 98 |
+
font-size: 0.8125rem;
|
| 99 |
+
max-width: 100%;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
#toolbar #searchbar:focus {
|
| 103 |
+
border-color: var(--body-quiet-color);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
#toolbar form input[type="submit"] {
|
| 107 |
+
border: 1px solid var(--border-color);
|
| 108 |
+
font-size: 0.8125rem;
|
| 109 |
+
padding: 4px 8px;
|
| 110 |
+
margin: 0;
|
| 111 |
+
vertical-align: middle;
|
| 112 |
+
background: var(--body-bg);
|
| 113 |
+
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
|
| 114 |
+
cursor: pointer;
|
| 115 |
+
color: var(--body-fg);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
#toolbar form input[type="submit"]:focus,
|
| 119 |
+
#toolbar form input[type="submit"]:hover {
|
| 120 |
+
border-color: var(--body-quiet-color);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
#changelist-search img {
|
| 124 |
+
vertical-align: middle;
|
| 125 |
+
margin-right: 4px;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
#changelist-search .help {
|
| 129 |
+
word-break: break-word;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* FILTER COLUMN */
|
| 133 |
+
|
| 134 |
+
#changelist-filter {
|
| 135 |
+
flex: 0 0 240px;
|
| 136 |
+
order: 1;
|
| 137 |
+
background: var(--darkened-bg);
|
| 138 |
+
border-left: none;
|
| 139 |
+
margin: 0 0 0 30px;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
@media (forced-colors: active) {
|
| 143 |
+
#changelist-filter {
|
| 144 |
+
border: 1px solid;
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
#changelist-filter h2 {
|
| 149 |
+
font-size: 0.875rem;
|
| 150 |
+
text-transform: uppercase;
|
| 151 |
+
letter-spacing: 0.5px;
|
| 152 |
+
padding: 5px 15px;
|
| 153 |
+
margin-bottom: 12px;
|
| 154 |
+
border-bottom: none;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
#changelist-filter h3,
|
| 158 |
+
#changelist-filter details summary {
|
| 159 |
+
font-weight: 400;
|
| 160 |
+
padding: 0 15px;
|
| 161 |
+
margin-bottom: 10px;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
#changelist-filter details summary > * {
|
| 165 |
+
display: inline;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
#changelist-filter details > summary {
|
| 169 |
+
list-style-type: none;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
#changelist-filter details > summary::-webkit-details-marker {
|
| 173 |
+
display: none;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
#changelist-filter details > summary::before {
|
| 177 |
+
content: '→';
|
| 178 |
+
font-weight: bold;
|
| 179 |
+
color: var(--link-hover-color);
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
#changelist-filter details[open] > summary::before {
|
| 183 |
+
content: '↓';
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
#changelist-filter ul {
|
| 187 |
+
margin: 5px 0;
|
| 188 |
+
padding: 0 15px 15px;
|
| 189 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
#changelist-filter ul:last-child {
|
| 193 |
+
border-bottom: none;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
#changelist-filter li {
|
| 197 |
+
list-style-type: none;
|
| 198 |
+
margin-left: 0;
|
| 199 |
+
padding-left: 0;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
#changelist-filter a {
|
| 203 |
+
display: block;
|
| 204 |
+
color: var(--body-quiet-color);
|
| 205 |
+
word-break: break-word;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
#changelist-filter li.selected {
|
| 209 |
+
border-left: 5px solid var(--hairline-color);
|
| 210 |
+
padding-left: 10px;
|
| 211 |
+
margin-left: -15px;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
#changelist-filter li.selected a {
|
| 215 |
+
color: var(--link-selected-fg);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
#changelist-filter a:focus, #changelist-filter a:hover,
|
| 219 |
+
#changelist-filter li.selected a:focus,
|
| 220 |
+
#changelist-filter li.selected a:hover {
|
| 221 |
+
color: var(--link-hover-color);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
#changelist-filter #changelist-filter-extra-actions {
|
| 225 |
+
font-size: 0.8125rem;
|
| 226 |
+
margin-bottom: 10px;
|
| 227 |
+
border-bottom: 1px solid var(--hairline-color);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
/* DATE DRILLDOWN */
|
| 231 |
+
|
| 232 |
+
.change-list .toplinks {
|
| 233 |
+
display: flex;
|
| 234 |
+
padding-bottom: 5px;
|
| 235 |
+
flex-wrap: wrap;
|
| 236 |
+
gap: 3px 17px;
|
| 237 |
+
font-weight: bold;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.change-list .toplinks a {
|
| 241 |
+
font-size: 0.8125rem;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.change-list .toplinks .date-back {
|
| 245 |
+
color: var(--body-quiet-color);
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
.change-list .toplinks .date-back:focus,
|
| 249 |
+
.change-list .toplinks .date-back:hover {
|
| 250 |
+
color: var(--link-hover-color);
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/* ACTIONS */
|
| 254 |
+
|
| 255 |
+
.filtered .actions {
|
| 256 |
+
border-right: none;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
#changelist table input {
|
| 260 |
+
margin: 0;
|
| 261 |
+
vertical-align: baseline;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
/* Once the :has() pseudo-class is supported by all browsers, the tr.selected
|
| 265 |
+
selector and the JS adding the class can be removed. */
|
| 266 |
+
#changelist tbody tr.selected {
|
| 267 |
+
background-color: var(--selected-row);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
#changelist tbody tr:has(.action-select:checked) {
|
| 271 |
+
background-color: var(--selected-row);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
@media (forced-colors: active) {
|
| 275 |
+
#changelist tbody tr.selected {
|
| 276 |
+
background-color: SelectedItem;
|
| 277 |
+
}
|
| 278 |
+
#changelist tbody tr:has(.action-select:checked) {
|
| 279 |
+
background-color: SelectedItem;
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
#changelist .actions {
|
| 284 |
+
padding: 10px;
|
| 285 |
+
background: var(--body-bg);
|
| 286 |
+
border-top: none;
|
| 287 |
+
border-bottom: none;
|
| 288 |
+
line-height: 1.5rem;
|
| 289 |
+
color: var(--body-quiet-color);
|
| 290 |
+
width: 100%;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
#changelist .actions span.all,
|
| 294 |
+
#changelist .actions span.action-counter,
|
| 295 |
+
#changelist .actions span.clear,
|
| 296 |
+
#changelist .actions span.question {
|
| 297 |
+
font-size: 0.8125rem;
|
| 298 |
+
margin: 0 0.5em;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
#changelist .actions:last-child {
|
| 302 |
+
border-bottom: none;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
#changelist .actions select {
|
| 306 |
+
vertical-align: top;
|
| 307 |
+
height: 1.5rem;
|
| 308 |
+
color: var(--body-fg);
|
| 309 |
+
border: 1px solid var(--border-color);
|
| 310 |
+
border-radius: 4px;
|
| 311 |
+
font-size: 0.875rem;
|
| 312 |
+
padding: 0 0 0 4px;
|
| 313 |
+
margin: 0;
|
| 314 |
+
margin-left: 10px;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
#changelist .actions select:focus {
|
| 318 |
+
border-color: var(--body-quiet-color);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
#changelist .actions label {
|
| 322 |
+
display: inline-block;
|
| 323 |
+
vertical-align: middle;
|
| 324 |
+
font-size: 0.8125rem;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
#changelist .actions .button {
|
| 328 |
+
font-size: 0.8125rem;
|
| 329 |
+
border: 1px solid var(--border-color);
|
| 330 |
+
border-radius: 4px;
|
| 331 |
+
background: var(--body-bg);
|
| 332 |
+
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
|
| 333 |
+
cursor: pointer;
|
| 334 |
+
height: 1.5rem;
|
| 335 |
+
line-height: 1;
|
| 336 |
+
padding: 4px 8px;
|
| 337 |
+
margin: 0;
|
| 338 |
+
color: var(--body-fg);
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
#changelist .actions .button:focus, #changelist .actions .button:hover {
|
| 342 |
+
border-color: var(--body-quiet-color);
|
| 343 |
+
}
|
static/admin/css/dark_mode.css
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@media (prefers-color-scheme: dark) {
|
| 2 |
+
:root {
|
| 3 |
+
--primary: #264b5d;
|
| 4 |
+
--primary-fg: #f7f7f7;
|
| 5 |
+
|
| 6 |
+
--body-fg: #eeeeee;
|
| 7 |
+
--body-bg: #121212;
|
| 8 |
+
--body-quiet-color: #d0d0d0;
|
| 9 |
+
--body-medium-color: #e0e0e0;
|
| 10 |
+
--body-loud-color: #ffffff;
|
| 11 |
+
|
| 12 |
+
--breadcrumbs-link-fg: #e0e0e0;
|
| 13 |
+
--breadcrumbs-bg: var(--primary);
|
| 14 |
+
|
| 15 |
+
--link-fg: #81d4fa;
|
| 16 |
+
--link-hover-color: #4ac1f7;
|
| 17 |
+
--link-selected-fg: #6f94c6;
|
| 18 |
+
|
| 19 |
+
--hairline-color: #272727;
|
| 20 |
+
--border-color: #353535;
|
| 21 |
+
|
| 22 |
+
--error-fg: #e35f5f;
|
| 23 |
+
--message-success-bg: #006b1b;
|
| 24 |
+
--message-warning-bg: #583305;
|
| 25 |
+
--message-error-bg: #570808;
|
| 26 |
+
|
| 27 |
+
--darkened-bg: #212121;
|
| 28 |
+
--selected-bg: #1b1b1b;
|
| 29 |
+
--selected-row: #00363a;
|
| 30 |
+
|
| 31 |
+
--close-button-bg: #333333;
|
| 32 |
+
--close-button-hover-bg: #666666;
|
| 33 |
+
|
| 34 |
+
color-scheme: dark;
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
html[data-theme="dark"] {
|
| 40 |
+
--primary: #264b5d;
|
| 41 |
+
--primary-fg: #f7f7f7;
|
| 42 |
+
|
| 43 |
+
--body-fg: #eeeeee;
|
| 44 |
+
--body-bg: #121212;
|
| 45 |
+
--body-quiet-color: #d0d0d0;
|
| 46 |
+
--body-medium-color: #e0e0e0;
|
| 47 |
+
--body-loud-color: #ffffff;
|
| 48 |
+
|
| 49 |
+
--breadcrumbs-link-fg: #e0e0e0;
|
| 50 |
+
--breadcrumbs-bg: var(--primary);
|
| 51 |
+
|
| 52 |
+
--link-fg: #81d4fa;
|
| 53 |
+
--link-hover-color: #4ac1f7;
|
| 54 |
+
--link-selected-fg: #6f94c6;
|
| 55 |
+
|
| 56 |
+
--hairline-color: #272727;
|
| 57 |
+
--border-color: #353535;
|
| 58 |
+
|
| 59 |
+
--error-fg: #e35f5f;
|
| 60 |
+
--message-success-bg: #006b1b;
|
| 61 |
+
--message-warning-bg: #583305;
|
| 62 |
+
--message-error-bg: #570808;
|
| 63 |
+
|
| 64 |
+
--darkened-bg: #212121;
|
| 65 |
+
--selected-bg: #1b1b1b;
|
| 66 |
+
--selected-row: #00363a;
|
| 67 |
+
|
| 68 |
+
--close-button-bg: #333333;
|
| 69 |
+
--close-button-hover-bg: #666666;
|
| 70 |
+
|
| 71 |
+
color-scheme: dark;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/* THEME SWITCH */
|
| 75 |
+
.theme-toggle {
|
| 76 |
+
cursor: pointer;
|
| 77 |
+
border: none;
|
| 78 |
+
padding: 0;
|
| 79 |
+
background: transparent;
|
| 80 |
+
vertical-align: middle;
|
| 81 |
+
margin-inline-start: 5px;
|
| 82 |
+
margin-top: -1px;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.theme-toggle svg {
|
| 86 |
+
vertical-align: middle;
|
| 87 |
+
height: 1rem;
|
| 88 |
+
width: 1rem;
|
| 89 |
+
display: none;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
/*
|
| 93 |
+
Fully hide screen reader text so we only show the one matching the current
|
| 94 |
+
theme.
|
| 95 |
+
*/
|
| 96 |
+
.theme-toggle .visually-hidden {
|
| 97 |
+
display: none;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
html[data-theme="auto"] .theme-toggle .theme-label-when-auto {
|
| 101 |
+
display: block;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
html[data-theme="dark"] .theme-toggle .theme-label-when-dark {
|
| 105 |
+
display: block;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
html[data-theme="light"] .theme-toggle .theme-label-when-light {
|
| 109 |
+
display: block;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/* ICONS */
|
| 113 |
+
.theme-toggle svg.theme-icon-when-auto,
|
| 114 |
+
.theme-toggle svg.theme-icon-when-dark,
|
| 115 |
+
.theme-toggle svg.theme-icon-when-light {
|
| 116 |
+
fill: var(--header-link-color);
|
| 117 |
+
color: var(--header-bg);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto {
|
| 121 |
+
display: block;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark {
|
| 125 |
+
display: block;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
html[data-theme="light"] .theme-toggle svg.theme-icon-when-light {
|
| 129 |
+
display: block;
|
| 130 |
+
}
|