Compare commits

...

8 Commits

Author SHA1 Message Date
Emmy D'Anello 14878fce86
Improve mobile chat
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 23:20:15 +02:00
Emmy D'Anello 13f6b1972f
Notifications use the PNG logo
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 20:37:23 +02:00
Emmy D'Anello 59f5667f52
Automatically scroll to bottom
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 20:23:55 +02:00
Emmy D'Anello be6e5b03c9
Chat is restricted to authenticated users
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 19:24:01 +02:00
Emmy D'Anello 363ed35fa1
Simplify chat views
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 19:12:11 +02:00
Emmy D'Anello a50e865ef0
Add feature to install chat on the home screen
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 19:11:03 +02:00
Emmy D'Anello badbb2567e
Add fullscreen mode for chat
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 16:16:57 +02:00
Emmy D'Anello 39085a6303
Fill channel selector using JavaScript
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-27 14:50:25 +02:00
13 changed files with 342 additions and 102 deletions

View File

@ -18,10 +18,10 @@ let selected_channel_id = null
* @return Notification
*/
function showNotification(title, body, timeout = 5000) {
let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"})
if (timeout)
setTimeout(() => notif.close(), timeout)
return notif
Notification.requestPermission().then((status) => {
if (status === 'granted')
new Notification(title, {'body': body, 'icon': "/static/tfjm-192.png"})
})
}
function selectChannel(channel_id) {
@ -33,6 +33,8 @@ function selectChannel(channel_id) {
selected_channel_id = channel_id
window.history.replaceState({}, null, `#channel-${channel['id']}`)
let channelTitle = document.getElementById('channel-title')
channelTitle.innerText = channel['name']
@ -60,21 +62,51 @@ function sendMessage() {
function setChannels(new_channels) {
channels = {}
let navTab = document.getElementById('nav-channels-tab')
navTab.innerHTML = ''
for (let channel of new_channels) {
channels[channel['id']] = channel
if (!messages[channel['id']])
messages[channel['id']] = new Map()
let navItem = document.createElement('li')
navItem.classList.add('list-group-item')
navItem.id = `tab-channel-${channel['id']}`
navItem.setAttribute('data-bs-dismiss', 'offcanvas')
navItem.onclick = () => selectChannel(channel['id'])
navTab.appendChild(navItem)
let channelButton = document.createElement('button')
channelButton.classList.add('nav-link')
channelButton.type = 'button'
channelButton.innerText = channel['name']
navItem.appendChild(channelButton)
fetchMessages(channel['id'])
}
if (new_channels && (!selected_channel_id || !channels[selected_channel_id]))
selectChannel(Object.keys(channels)[0])
if (new_channels && (!selected_channel_id || !channels[selected_channel_id])) {
if (window.location.hash)
selectChannel(window.location.hash.substring(9))
else
selectChannel(Object.keys(channels)[0])
}
}
function receiveMessage(message) {
messages[message['channel_id']].push(message)
let scrollableContent = document.getElementById('chat-messages')
let isScrolledToBottom = scrollableContent.scrollHeight - scrollableContent.clientHeight <= scrollableContent.scrollTop + 1
messages[message['channel_id']].set(message['id'], message)
redrawMessages()
// Scroll to bottom if the user was already at the bottom
if (isScrolledToBottom)
scrollableContent.scrollTop = scrollableContent.scrollHeight - scrollableContent.clientHeight
if (message['content'].includes("@everyone"))
showNotification(channels[message['channel_id']]['name'], `${message['author']} : ${message['content']}`)
}
function fetchMessages(channel_id, offset = 0, limit = MAX_MESSAGES) {
@ -164,6 +196,20 @@ function redrawMessages() {
fetchMoreButton.classList.remove('d-none')
}
function toggleFullscreen() {
let chatContainer = document.getElementById('chat-container')
if (!chatContainer.getAttribute('data-fullscreen')) {
chatContainer.setAttribute('data-fullscreen', 'true')
chatContainer.classList.add('position-absolute', 'top-0', 'start-0', 'vh-100', 'z-3')
window.history.replaceState({}, null, `?fullscreen=1#channel-${selected_channel_id}`)
}
else {
chatContainer.removeAttribute('data-fullscreen')
chatContainer.classList.remove('position-absolute', 'top-0', 'start-0', 'vh-100', 'z-3')
window.history.replaceState({}, null, `?fullscreen=0#channel-${selected_channel_id}`)
}
}
document.addEventListener('DOMContentLoaded', () => {
/**
* Process the received data from the server.
@ -215,4 +261,25 @@ document.addEventListener('DOMContentLoaded', () => {
}
setupSocket()
let deferredPrompt = null
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault()
deferredPrompt = e
let btn = document.getElementById('install-app-home-screen')
let alert = document.getElementById('alert-download-chat-app')
btn.classList.remove('d-none')
alert.classList.remove('d-none')
btn.onclick = function () {
deferredPrompt.prompt()
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
deferredPrompt = null
btn.classList.add('d-none')
alert.classList.add('d-none')
}
})
}
})
})

View File

@ -0,0 +1,29 @@
{
"background_color": "white",
"description": "Chat pour le TFJM²",
"display": "standalone",
"icons": [
{
"src": "tfjm-square.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "maskable"
},
{
"src": "tfjm-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "tfjm-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
}
],
"name": "Chat TFJM²",
"short_name": "Chat TFJM²",
"start_url": "/chat/fullscreen",
"theme_color": "black"
}

View File

@ -3,60 +3,14 @@
{% load static %}
{% load i18n %}
{% block content %}
<noscript>
{% trans "JavaScript must be enabled on your browser to access chat." %}
</noscript>
<div class="offcanvas offcanvas-start" tabindex="-1" id="channelSelector" aria-labelledby="offcanvasExampleLabel">
<div class="offcanvas-header">
<h4 class="offcanvas-title" id="offcanvasExampleLabel">{% trans "Chat channels" %}</h4>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<ul class="list-group list-group-flush" id="nav-channels-tab">
{% for channel in channels %}
<li class="list-group-item" id="tab-channel-{{ channel.id }}" data-bs-dismiss="offcanvas"
onclick="selectChannel({{ channel.id }})">
<button class="nav-link">{{ channel.name }}</button>
</li>
{% endfor %}
</ul>
</div>
</div>
{% block extracss %}
<link rel="manifest" href="{% static "chat.webmanifest" %}">
{% endblock %}
<div class="card tab-content w-100 mh-100" style="height: 95vh" id="nav-channels-content">
<div class="card-header">
<h3>
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#channelSelector"
aria-controls="channelSelector" aria-expanded="false" aria-label="Toggle channel selector">
<span class="navbar-toggler-icon"></span>
</button>
<span id="channel-title"></span>
</h3>
</div>
<div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages">
<div class="text-center d-none" id="fetch-previous-messages">
<a href="#" class="nav-link" onclick="event.preventDefault(); fetchPreviousMessages()">
{% trans "Fetch previous messages…" %}
</a>
<hr>
</div>
<ul class="list-group list-group-flush" id="message-list"></ul>
</div>
<div class="card-footer mt-auto">
<form onsubmit="event.preventDefault(); sendMessage()">
<div class="input-group">
<label for="input-message" class="input-group-text">
<i class="fas fa-comment"></i>
</label>
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message" %}" autocomplete="off">
<button class="input-group-text btn btn-success" type="submit">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</form>
</div>
</div>
{% block content-title %}{% endblock %}
{% block content %}
{% include "chat/content.html" %}
{% endblock %}
{% block extrajavascript %}

View File

@ -0,0 +1,61 @@
{% load i18n %}
<noscript>
{% trans "JavaScript must be enabled on your browser to access chat." %}
</noscript>
<div class="offcanvas offcanvas-start" tabindex="-1" id="channelSelector" aria-labelledby="offcanvasExampleLabel">
<div class="offcanvas-header">
<h4 class="offcanvas-title" id="offcanvasExampleLabel">{% trans "Chat channels" %}</h4>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<ul class="list-group list-group-flush" id="nav-channels-tab"></ul>
</div>
</div>
<div class="alert alert-info d-none" id="alert-download-chat-app">
{% trans "You can install a shortcut to the chat on your home screen using the download button on the header." %}
</div>
<div class="card tab-content w-100 mh-100{% if request.GET.fullscreen == '1' or fullscreen %} position-absolute top-0 start-0 vh-100 z-3{% endif %}"
style="height: 95vh" id="chat-container">
<div class="card-header">
<h3>
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#channelSelector"
aria-controls="channelSelector" aria-expanded="false" aria-label="Toggle channel selector">
<span class="navbar-toggler-icon"></span>
</button>
<span id="channel-title"></span>
{% if not fullscreen %}
<button class="btn float-end" type="button" onclick="toggleFullscreen()" title="{% trans "Toggle fullscreen mode" %}">
<i class="fas fa-expand"></i>
</button>
{% endif %}
<button class="btn float-end d-none" type="button" id="install-app-home-screen" title="{% trans "Install app on home screen" %}">
<i class="fas fa-download"></i>
</button>
</h3>
</div>
<div class="card-body d-flex flex-column-reverse flex-grow-0 overflow-y-scroll" id="chat-messages">
<div class="text-center d-none" id="fetch-previous-messages">
<a href="#" class="nav-link" onclick="event.preventDefault(); fetchPreviousMessages()">
{% trans "Fetch previous messages…" %}
</a>
<hr>
</div>
<ul class="list-group list-group-flush" id="message-list"></ul>
</div>
<div class="card-footer mt-auto">
<form onsubmit="event.preventDefault(); sendMessage()">
<div class="input-group">
<label for="input-message" class="input-group-text">
<i class="fas fa-comment"></i>
</label>
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message" %}" autofocus autocomplete="off">
<button class="input-group-text btn btn-success" type="submit">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,34 @@
{% load i18n static %}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html lang="{{ LANGUAGE_CODE|default:"fr" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>
Chat du TFJM²
</title>
<meta name="description" content="Chat du TFJM²">
{# Favicon #}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<meta name="theme-color" content="#ffffff">
{# Bootstrap CSS #}
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/all.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/v4-shims.css' %}">
{# Bootstrap JavaScript #}
<script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<link rel="manifest" href="{% static "chat.webmanifest" %}">
</head>
<body class="d-flex w-100 h-100 flex-column">
{% include "chat/content.html" with fullscreen=True %}
<script src="{% static 'theme.js' %}"></script>
<script src="{% static 'chat.js' %}"></script>
</body>
</html>

View File

@ -2,12 +2,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from .views import ChatView
from django.utils.translation import gettext_lazy as _
from tfjm.views import LoginRequiredTemplateView
app_name = 'chat'
urlpatterns = [
path('', ChatView.as_view(), name='chat'),
path('', LoginRequiredTemplateView.as_view(template_name="chat/chat.html",
extra_context={'title': _("Chat")}), name='chat'),
path('fullscreen/', LoginRequiredTemplateView.as_view(template_name="chat/fullscreen.html"), name='fullscreen'),
]

View File

@ -1,21 +0,0 @@
# Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from chat.models import Channel
class ChatView(LoginRequiredMixin, TemplateView):
"""
This view is the main interface of the chat system, which is working
with Javascript and websockets.
"""
template_name = "chat/chat.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
from asgiref.sync import async_to_sync
context['channels'] = async_to_sync(Channel.get_accessible_channels)(self.request.user, 'read')
return context

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-27 14:10+0200\n"
"POT-Creation-Date: 2024-04-27 18:49+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -143,22 +143,42 @@ msgstr "message"
msgid "messages"
msgstr "messages"
#: chat/templates/chat/chat.html:8
#: chat/templates/chat/chat.html:14
msgid "JavaScript must be enabled on your browser to access chat."
msgstr "JavaScript doit être activé sur votre navigateur pour accéder au chat."
#: chat/templates/chat/chat.html:12
#: chat/templates/chat/chat.html:18
msgid "Chat channels"
msgstr "Canaux de chat"
#: chat/templates/chat/chat.html:27
msgid ""
"You can install a shortcut to the chat on your home screen using the "
"download button on the header."
msgstr ""
"Vous pouvez installer un raccourci vers le chat sur votre écran d'accueil en "
"utilisant le bouton de téléchargement dans l'en-tête."
#: chat/templates/chat/chat.html:40
msgid "Toggle fullscreen mode"
msgstr "Inverse le mode plein écran"
#: chat/templates/chat/chat.html:44
msgid "Install app on home screen"
msgstr "Installer l'application sur l'écran d'accueil"
#: chat/templates/chat/chat.html:52
msgid "Fetch previous messages…"
msgstr "Récupérer les messages précédents…"
#: chat/templates/chat/chat.html:52
#: chat/templates/chat/chat.html:64
msgid "Send message…"
msgstr "Envoyer un message…"
#: chat/views.py:15 tfjm/templates/navbar.html:67
msgid "Chat"
msgstr "Chat"
#: draw/admin.py:39 draw/admin.py:57 draw/admin.py:75
#: participation/admin.py:109 participation/models.py:253
#: participation/tables.py:88
@ -3720,10 +3740,6 @@ msgstr "Mon équipe"
msgid "My participation"
msgstr "Ma participation"
#: tfjm/templates/navbar.html:67
msgid "Chat"
msgstr "Chat"
#: tfjm/templates/navbar.html:72
msgid "Administration"
msgstr "Administration"
@ -3744,7 +3760,7 @@ msgstr "S'inscrire"
msgid "My account"
msgstr "Mon compte"
#: tfjm/templates/navbar.html:115
#: tfjm/templates/navbar.html:117
msgid "Log out"
msgstr "Déconnexion"

BIN
tfjm/static/tfjm-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tfjm/static/tfjm-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 32 32"
version="1.1"
id="svg27"
sodipodi:docname="logo.svg"
width="32"
height="32"
inkscape:version="0.92.2 2405546, 2018-03-11">
<style>
path {
fill: black;
}
</style>
<metadata
id="metadata31">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs15">
<path
id="b"
d="m 2.58,-3.347 c 0.409,0 1.405,0.02 1.485,1.135 0.01,0.12 0.02,0.25 0.18,0.25 0.168,0 0.168,-0.14 0.168,-0.32 v -2.7 c 0,-0.159 0,-0.318 -0.169,-0.318 -0.13,0 -0.17,0.1 -0.18,0.21 -0.059,1.155 -0.756,1.354 -1.484,1.384 v -2.102 c 0,-0.668 0.19,-0.668 0.429,-0.668 h 0.468 c 1.275,0 1.923,0.688 1.983,1.375 0.01,0.08 0.02,0.23 0.179,0.23 0.17,0 0.17,-0.16 0.17,-0.33 v -1.295 c 0,-0.308 -0.02,-0.328 -0.33,-0.328 h -5 c -0.18,0 -0.34,0 -0.34,0.179 0,0.17 0.19,0.17 0.27,0.17 0.567,0 0.607,0.079 0.607,0.567 v 4.991 c 0,0.469 -0.03,0.568 -0.558,0.568 -0.15,0 -0.319,0 -0.319,0.17 C 0.14,0 0.3,0 0.48,0 h 2.878 c 0.18,0 0.33,0 0.33,-0.18 0,-0.169 -0.17,-0.169 -0.3,-0.169 -0.767,0 -0.807,-0.07 -0.807,-0.597 v -2.401 z m 2.88,-3.129 v 0.469 A 2.557,2.557 0 0 0 4.922,-6.476 Z M 4.065,-3.158 A 1.51,1.51 0 0 0 3.537,-3.547 c 0.189,-0.09 0.388,-0.249 0.528,-0.418 z m -2.7,-2.77 c 0,-0.12 0,-0.368 -0.08,-0.548 h 1.056 c -0.11,0.23 -0.11,0.558 -0.11,0.648 v 4.901 c 0,0.15 0,0.389 0.1,0.578 H 1.285 c 0.08,-0.179 0.08,-0.428 0.08,-0.548 v -5.03 z" />
<path
id="c"
d="m 1.564,-6.824 c -0.18,0 -0.339,0 -0.339,0.179 0,0.17 0.18,0.17 0.29,0.17 0.687,0 0.727,0.069 0.727,0.577 v 5.59 c 0,0.169 0,0.358 -0.17,0.527 -0.08,0.07 -0.239,0.18 -0.478,0.18 -0.07,0 -0.369,0 -0.369,-0.11 0,-0.08 0.04,-0.12 0.09,-0.17 A 0.704,0.704 0 0 0 0.777,-1.057 0.704,0.704 0 0 0 0.06,-0.359 c 0,0.629 0.637,1.106 1.604,1.106 1.106,0 2.042,-0.387 2.192,-1.614 0.01,-0.09 0.01,-0.647 0.01,-0.966 v -4.184 c 0,-0.449 0.139,-0.449 0.707,-0.459 0.09,0 0.17,-0.08 0.17,-0.17 0,-0.178 -0.15,-0.178 -0.33,-0.178 z M 0.867,0.239 C 0.767,0.19 0.408,0.02 0.408,-0.349 c 0,-0.259 0.22,-0.358 0.37,-0.358 0.168,0 0.368,0.12 0.368,0.348 0,0.15 -0.08,0.24 -0.12,0.27 -0.04,0.04 -0.13,0.139 -0.16,0.328 z M 2.59,-5.918 c 0,-0.11 0,-0.378 -0.09,-0.558 h 1.097 c -0.08,0.18 -0.08,0.369 -0.08,0.708 v 4.015 c 0,0.298 0,0.797 -0.01,0.896 C 3.427,-0.349 3.198,0.11 2.44,0.31 2.59,0.08 2.59,-0.109 2.59,-0.288 v -5.629 z" />
<path
id="d"
d="M 4.643,-2.092 2.74,-6.625 c -0.08,-0.2 -0.09,-0.2 -0.359,-0.2 H 0.528 c -0.18,0 -0.329,0 -0.329,0.18 0,0.17 0.18,0.17 0.23,0.17 0.119,0 0.388,0.02 0.607,0.099 v 5.32 c 0,0.21 0,0.648 -0.677,0.707 -0.19,0.02 -0.19,0.16 -0.19,0.17 C 0.17,0 0.33,0 0.51,0 h 1.543 c 0.18,0 0.33,0 0.33,-0.18 0,-0.089 -0.08,-0.159 -0.16,-0.169 -0.767,-0.06 -0.767,-0.478 -0.767,-0.707 v -4.961 l 0.01,-0.01 2.429,5.817 c 0.08,0.18 0.15,0.209 0.21,0.209 0.12,0 0.149,-0.08 0.199,-0.2 l 2.44,-5.827 0.01,0.01 v 4.961 c 0,0.21 0,0.648 -0.677,0.707 -0.19,0.02 -0.19,0.16 -0.19,0.17 0,0.179 0.16,0.179 0.34,0.179 h 2.66 c 0.179,0 0.328,0 0.328,-0.18 C 9.215,-0.27 9.135,-0.34 9.056,-0.35 8.289,-0.41 8.289,-0.828 8.289,-1.057 v -4.712 c 0,-0.21 0,-0.648 0.677,-0.708 0.1,-0.01 0.19,-0.06 0.19,-0.17 0,-0.178 -0.15,-0.178 -0.33,-0.178 H 6.905 c -0.259,0 -0.279,0 -0.369,0.209 z m -0.3,0.18 c 0.08,0.169 0.09,0.178 0.21,0.218 L 4.115,-0.638 H 4.095 L 1.823,-6.058 C 1.773,-6.187 1.693,-6.356 1.554,-6.476 h 0.867 l 1.923,4.563 z M 1.336,-0.35 h -0.17 c 0.02,-0.03 0.04,-0.06 0.06,-0.08 0.01,-0.01 0.01,-0.02 0.02,-0.03 z M 7.104,-6.477 H 8.16 c -0.219,0.25 -0.219,0.508 -0.219,0.688 v 4.752 c 0,0.18 0,0.438 0.23,0.687 H 6.883 c 0.22,-0.249 0.22,-0.508 0.22,-0.687 v -5.44 z" />
<path
id="a"
d="m 4.135,-6.466 c 1.305,0.07 1.793,0.917 1.833,1.385 0.01,0.15 0.02,0.299 0.179,0.299 0.18,0 0.18,-0.17 0.18,-0.359 v -1.325 c 0,-0.348 -0.04,-0.358 -0.34,-0.358 H 0.658 c -0.308,0 -0.328,0.02 -0.328,0.318 V -5.1 c 0,0.16 0,0.319 0.17,0.319 0.17,0 0.178,-0.18 0.178,-0.2 0.04,-0.826 0.788,-1.424 1.834,-1.484 v 5.54 c 0,0.498 -0.04,0.577 -0.668,0.577 -0.12,0 -0.299,0 -0.299,0.17 0,0.179 0.16,0.179 0.339,0.179 h 2.89 C 4.95,0 5.1,0 5.1,-0.18 c 0,-0.169 -0.17,-0.169 -0.28,-0.169 -0.647,0 -0.686,-0.07 -0.686,-0.578 v -5.539 z m -3.458,-0.01 h 0.598 c -0.249,0.15 -0.458,0.349 -0.598,0.518 z m 5.3,0 v 0.528 A 2.606,2.606 0 0 0 5.37,-6.476 H 5.978 Z M 2.77,-0.349 c 0.09,-0.179 0.09,-0.428 0.09,-0.558 v -5.569 h 0.926 v 5.57 c 0,0.129 0,0.378 0.09,0.557 H 2.77 Z" />
<path
id="e"
d="M 3.522,-1.27 H 3.285 c -0.021,0.154 -0.091,0.566 -0.182,0.635 -0.055,0.042 -0.592,0.042 -0.69,0.042 H 1.13 c 0.732,-0.648 0.976,-0.844 1.395,-1.171 0.516,-0.412 0.997,-0.844 0.997,-1.507 0,-0.844 -0.74,-1.36 -1.632,-1.36 -0.865,0 -1.45,0.607 -1.45,1.249 0,0.355 0.3,0.39 0.369,0.39 0.167,0 0.37,-0.118 0.37,-0.37 0,-0.125 -0.05,-0.369 -0.412,-0.369 0.216,-0.495 0.69,-0.649 1.018,-0.649 0.698,0 1.06,0.544 1.06,1.11 0,0.606 -0.432,1.087 -0.655,1.338 l -1.68,1.66 C 0.44,-0.209 0.44,-0.195 0.44,0 h 2.873 z" />
</defs>
<rect width="100%" height="100%"
rx="10px" ry="10px" stroke-linejoin="round"
style="fill: white;" />
<use
x="0.5"
y="19.5"
xlink:href="#a"
id="use17"
width="100%"
height="100%" />
<use
x="7.5"
y="19.5"
xlink:href="#b"
id="use19"
width="100%"
height="100%" />
<use
x="13"
y="19.5"
xlink:href="#c"
id="use21"
width="100%"
height="100%" />
<use
x="18"
y="19.5"
xlink:href="#d"
id="use23"
width="100%"
height="100%" />
<use
x="27"
y="14"
xlink:href="#e"
id="use25"
width="100%"
height="100%" />
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -61,12 +61,12 @@
</a>
</li>
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="{% url "chat:chat" %}">
<i class="fas fa-comments"></i> {% trans "Chat" %}
</a>
</li>
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="{% url "chat:chat" %}">
<i class="fas fa-comments"></i> {% trans "Chat" %}
</a>
</li>
{% if user.registration.is_admin %}
<li class="nav-item active">
<a class="nav-link" href="{% url "admin:index" %}"><i class="fas fa-cog"></i> {% trans "Administration" %}</a>
@ -111,9 +111,12 @@
</a>
</li>
<li>
<a class="dropdown-item" href="{% url "logout" %}">
<i class="fas fa-sign-out-alt"></i> {% trans "Log out" %}
</a>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button class="dropdown-item">
<i class="fas fa-sign-out-alt"></i> {% trans "Log out" %}
</button>
</form>
</li>
</ul>
</li>

View File

@ -3,6 +3,7 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.views.generic import TemplateView
from haystack.generic_views import SearchView
@ -40,5 +41,9 @@ class UserRegistrationMixin(LoginRequiredMixin):
return super().dispatch(request, *args, **kwargs)
class LoginRequiredTemplateView(LoginRequiredMixin, TemplateView):
pass
class AdminSearchView(AdminMixin, SearchView):
pass