News moderation buttons directly on the home page

This commit is contained in:
imperosol 2025-01-20 18:02:24 +01:00
parent 185fb70dda
commit 2ca3286f0f
7 changed files with 244 additions and 78 deletions

View File

@ -0,0 +1,39 @@
import { exportToHtml } from "#core:utils/globals";
import Alpine from "alpinejs";
import { newsDeleteNews, newsModerateNews } from "#openapi";
// This will be used in jinja templates,
// so we cannot use real enums as those are purely an abstraction of Typescript
const AlertState = {
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
PENDING: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
MODERATED: 2,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
DELETED: 3,
};
exportToHtml("AlertState", AlertState);
document.addEventListener("alpine:init", () => {
Alpine.data("moderationAlert", (newsId: number) => ({
state: AlertState.PENDING,
newsId: newsId as number,
loading: false,
async moderateNews() {
this.loading = true;
// biome-ignore lint/style/useNamingConvention: api is snake case
await newsModerateNews({ path: { news_id: this.newsId } });
this.state = AlertState.MODERATED;
this.loading = false;
},
async deleteNews() {
this.loading = true;
// biome-ignore lint/style/useNamingConvention: api is snake case
await newsDeleteNews({ path: { news_id: this.newsId } });
this.state = AlertState.DELETED;
this.loading = false;
},
}));
});

View File

@ -171,7 +171,9 @@
} }
.news_event { .news_event {
display: block; display: flex;
flex-direction: column;
gap: .5em;
padding: 1em; padding: 1em;
header { header {

View File

@ -0,0 +1,73 @@
{% macro news_moderation_alert(news, user, alpineState = None) %}
{# An alert to display on top of non moderated news,
with actions to either moderate or delete them.
The current state of the alert is accessible through
the given `alpineState` variable.
This state is a `AlertState`, as defined in `moderation-alert-index.ts`
Example :
```jinja
<div x-data="{state: AlertState.PENDING}">
{{ news_moderation_alert(news, user, "state") }}
</div>
```
Args:
news: The `News` object to which this alert is related
user: The request.user
alpineState: An alpine variable name
Warning:
If you use this macro, you must also include `moderation-alert-index.ts`
in your template.
#}
<div
x-data="moderationAlert({{ news.id }})"
{% if alpineState %}
x-modelable="{{ alpineState }}"
x-model="state"
{% endif %}
>
<template x-if="state === AlertState.PENDING">
<div class="alert alert-yellow">
<div class="alert-main">
<strong>{% trans %}Waiting moderation{% endtrans %}</strong>
<p>
{% trans trimmed %}
This news isn't moderated and is visible
only by its author and the communication admins.
{% endtrans %}
</p>
<p>
{% trans trimmed %}
It will stay hidden for other users until it has been moderated.
{% endtrans %}
</p>
</div>
{% if user.has_perm("com.moderate_news") %}
<span class="alert-aside" :aria-busy="loading">
<button class="btn btn-green" @click="moderateNews()" :disabled="loading">
<i class="fa fa-check"></i> {% trans %}Moderate{% endtrans %}
</button>
{% endif %}
{% if user.has_perm("com.delete_news") %}
<button class="btn btn-red" @click="deleteNews()" :disabled="loading">
<i class="fa fa-trash-can"></i> {% trans %}Delete{% endtrans %}
</button>
</span>
{% endif %}
</div>
</template>
<template x-if="state === AlertState.MODERATED">
<div class="alert alert-green">
{% trans %}News moderated{% endtrans %}
</div>
</template>
<template x-if="state === AlertState.DELETED">
<div class="alert alert-red">
{% trans %}News deleted{% endtrans %}
</div>
</template>
</div>
{% endmacro %}

View File

@ -1,5 +1,6 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import user_profile_link, facebook_share, tweet, link_news_logo, gen_news_metatags %} {% from 'core/macros.jinja' import user_profile_link, facebook_share, tweet, link_news_logo, gen_news_metatags %}
{% from "com/macros.jinja" import news_moderation_alert %}
{% block title %} {% block title %}
{% trans %}News{% endtrans %} - {% trans %}News{% endtrans %} -
@ -16,39 +17,49 @@
<link rel="stylesheet" href="{{ static('com/css/news-detail.scss') }}"> <link rel="stylesheet" href="{{ static('com/css/news-detail.scss') }}">
{% endblock %} {% endblock %}
{% block additional_js %}
<script type="module" src={{ static("bundled/com/components/moderation-alert-index.ts") }}></script>
{% endblock %}
{% block content %} {% block content %}
<p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p> <p><a href="{{ url('com:news_list') }}">{% trans %}Back to news{% endtrans %}</a></p>
<section id="news_details"> <div x-data="{newsState: AlertState.PENDING}">
<div class="club_logo">
<img src="{{ link_news_logo(news)}}" alt="{{ news.club }}" /> {% if not news.is_moderated %}
<a href="{{ news.club.get_absolute_url() }}">{{ news.club }}</a> {{ news_moderation_alert(news, user, "newsState") }}
</div> {% endif %}
<h4>{{ news.title }}</h4> <article id="news_details" x-show="newsState !== AlertState.DELETED">
<p class="date"> <div class="club_logo">
<span>{{ date.start_date|localtime|date(DATETIME_FORMAT) }} <img src="{{ link_news_logo(news)}}" alt="{{ news.club }}" />
{{ date.start_date|localtime|time(DATETIME_FORMAT) }}</span> - <a href="{{ news.club.get_absolute_url() }}">{{ news.club }}</a>
<span>{{ date.end_date|localtime|date(DATETIME_FORMAT) }}
{{ date.end_date|localtime|time(DATETIME_FORMAT) }}</span>
</p>
<div class="news_content">
<div><em>{{ news.summary|markdown }}</em></div>
<br/>
<div>{{ news.content|markdown }}</div>
{{ facebook_share(news) }}
{{ tweet(news) }}
<div class="news_meta">
<p>{% trans %}Author: {% endtrans %}{{ user_profile_link(news.author) }}</p>
{% if news.moderator %}
<p>{% trans %}Moderator: {% endtrans %}{{ user_profile_link(news.moderator) }}</p>
{% elif user.is_com_admin %}
<p> <a href="{{ url('com:news_moderate', news_id=news.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
{% endif %}
{% if user.can_edit(news) %}
<p> <a href="{{ url('com:news_edit', news_id=news.id) }}">{% trans %}Edit (will be moderated again){% endtrans %}</a></p>
{% endif %}
</div> </div>
</div> <h4>{{ news.title }}</h4>
</section> <p class="date">
<span>{{ date.start_date|localtime|date(DATETIME_FORMAT) }}
{{ date.start_date|localtime|time(DATETIME_FORMAT) }}</span> -
<span>{{ date.end_date|localtime|date(DATETIME_FORMAT) }}
{{ date.end_date|localtime|time(DATETIME_FORMAT) }}</span>
</p>
<div class="news_content">
<div><em>{{ news.summary|markdown }}</em></div>
<br/>
<div>{{ news.content|markdown }}</div>
{{ facebook_share(news) }}
{{ tweet(news) }}
<div class="news_meta">
<p>{% trans %}Author: {% endtrans %}{{ user_profile_link(news.author) }}</p>
{% if news.moderator %}
<p>{% trans %}Moderator: {% endtrans %}{{ user_profile_link(news.moderator) }}</p>
{% elif user.is_com_admin %}
<p> <a href="{{ url('com:news_moderate', news_id=news.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
{% endif %}
{% if user.can_edit(news) %}
<p> <a href="{{ url('com:news_edit', news_id=news.id) }}">{% trans %}Edit (will be moderated again){% endtrans %}</a></p>
{% endif %}
</div>
</div>
</article>
</div>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,5 @@
{% extends "core/base.jinja" %} {% extends "core/base.jinja" %}
{% from "com/macros.jinja" import news_moderation_alert %}
{% block title %} {% block title %}
{% trans %}News{% endtrans %} {% trans %}News{% endtrans %}
@ -14,6 +15,7 @@
{% block additional_js %} {% block additional_js %}
<script type="module" src={{ static("bundled/com/components/ics-calendar-index.ts") }}></script> <script type="module" src={{ static("bundled/com/components/ics-calendar-index.ts") }}></script>
<script type="module" src={{ static("bundled/com/components/moderation-alert-index.ts") }}></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -46,32 +48,39 @@
</div> </div>
<div class="news_events_group_items"> <div class="news_events_group_items">
{% for date in dates_group %} {% for date in dates_group %}
<article class="news_event"> <article class="news_event" x-data="{newsState: AlertState.PENDING}">
<header class="row gap"> {% if not date.news.is_moderated %}
{% if date.news.club.logo %} {# if a non moderated news is in the object list,
<img src="{{ date.news.club.logo.url }}" alt="{{ date.news.club }}"/> the logged user is either an admin or the news author #}
{% else %} {{ news_moderation_alert(date.news, user, "newsState") }}
<img src="{{ static("com/img/news.png") }}" alt="{{ date.news.club }}"/> {% endif %}
{% endif %} <div x-show="newsState !== AlertState.DELETED">
<div class="header_content"> <header class="row gap">
<h4> {% if date.news.club.logo %}
<a href="{{ url('com:news_detail', news_id=date.news_id) }}"> <img src="{{ date.news.club.logo.url }}" alt="{{ date.news.club }}"/>
{{ date.news.title }} {% else %}
</a> <img src="{{ static("com/img/news.png") }}" alt="{{ date.news.club }}"/>
</h4> {% endif %}
<a href="{{ date.news.club.get_absolute_url() }}">{{ date.news.club }}</a> <div class="header_content">
<div class="news_date"> <h4>
<time datetime="{{ date.start_date.isoformat(timespec="seconds") }}"> <a href="{{ url('com:news_detail', news_id=date.news_id) }}">
{{ date.start_date|localtime|time(DATETIME_FORMAT) }} {{ date.news.title }}
</time> - </a>
<time datetime="{{ date.end_date.isoformat(timespec="seconds") }}"> </h4>
{{ date.end_date|localtime|time(DATETIME_FORMAT) }} <a href="{{ date.news.club.get_absolute_url() }}">{{ date.news.club }}</a>
</time> <div class="news_date">
<time datetime="{{ date.start_date.isoformat(timespec="seconds") }}">
{{ date.start_date|localtime|time(DATETIME_FORMAT) }}
</time> -
<time datetime="{{ date.end_date.isoformat(timespec="seconds") }}">
{{ date.end_date|localtime|time(DATETIME_FORMAT) }}
</time>
</div>
</div> </div>
</header>
<div class="news_content markdown">
{{ date.news.summary|markdown }}
</div> </div>
</header>
<div class="news_content markdown">
{{ date.news.summary|markdown }}
</div> </div>
</article> </article>
{% endfor %} {% endfor %}

View File

@ -244,6 +244,20 @@ body {
} }
} }
&.btn-green {
$bg-color: rgba(0, 210, 83, 0.4);
background-color: $bg-color;
color: $black-color;
&:not(:disabled):hover {
background-color: darken($bg-color, 15%);
}
&:disabled {
background-color: lighten($bg-color, 15%);
}
}
&.btn-red { &.btn-red {
background-color: #fc8181; background-color: #fc8181;
color: black; color: black;

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-19 18:12+0100\n" "POT-Creation-Date: 2025-01-20 17:35+0100\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -310,7 +310,7 @@ msgstr "Compte en banque : "
#: accounting/templates/accounting/club_account_details.jinja #: accounting/templates/accounting/club_account_details.jinja
#: accounting/templates/accounting/label_list.jinja #: accounting/templates/accounting/label_list.jinja
#: club/templates/club/club_sellings.jinja club/templates/club/mailing.jinja #: club/templates/club/club_sellings.jinja club/templates/club/mailing.jinja
#: com/templates/com/mailing_admin.jinja #: com/templates/com/macros.jinja com/templates/com/mailing_admin.jinja
#: com/templates/com/news_admin_list.jinja com/templates/com/poster_edit.jinja #: com/templates/com/news_admin_list.jinja com/templates/com/poster_edit.jinja
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja #: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja
#: core/templates/core/file_detail.jinja #: core/templates/core/file_detail.jinja
@ -935,10 +935,6 @@ msgstr "rôle"
msgid "description" msgid "description"
msgstr "description" msgstr "description"
#: club/models.py
msgid "past member"
msgstr "ancien membre"
#: club/models.py #: club/models.py
msgid "Email address" msgid "Email address"
msgstr "Adresse email" msgstr "Adresse email"
@ -1408,12 +1404,25 @@ msgstr "temps d'affichage"
msgid "Begin date should be before end date" msgid "Begin date should be before end date"
msgstr "La date de début doit être avant celle de fin" msgstr "La date de début doit être avant celle de fin"
#: com/templates/com/mailing_admin.jinja com/views.py #: com/templates/com/macros.jinja
#: core/templates/core/user_tools.jinja msgid "Waiting moderation"
msgid "Mailing lists administration" msgstr "En attente de modération"
msgstr "Administration des mailing listes"
#: com/templates/com/mailing_admin.jinja #: com/templates/com/macros.jinja
msgid ""
"This news isn't moderated and is visible only by its author and the "
"communication admins."
msgstr ""
"Cette nouvelle n'est pas modérée et n'est visible que par son auteur et les "
"admins communication."
#: com/templates/com/macros.jinja
msgid "It will stay hidden for other users until it has been moderated."
msgstr ""
"Elle sera cachée pour les autres utilisateurs tant qu'elle ne sera pas "
"modérée."
#: com/templates/com/macros.jinja com/templates/com/mailing_admin.jinja
#: com/templates/com/news_admin_list.jinja com/templates/com/news_detail.jinja #: com/templates/com/news_admin_list.jinja com/templates/com/news_detail.jinja
#: core/templates/core/file_detail.jinja #: core/templates/core/file_detail.jinja
#: core/templates/core/file_moderation.jinja sas/templates/sas/moderation.jinja #: core/templates/core/file_moderation.jinja sas/templates/sas/moderation.jinja
@ -1421,6 +1430,19 @@ msgstr "Administration des mailing listes"
msgid "Moderate" msgid "Moderate"
msgstr "Modérer" msgstr "Modérer"
#: com/templates/com/macros.jinja
msgid "News moderated"
msgstr "Nouvelle modérée"
#: com/templates/com/macros.jinja
msgid "News deleted"
msgstr "Nouvelle supprimée"
#: com/templates/com/mailing_admin.jinja com/views.py
#: core/templates/core/user_tools.jinja
msgid "Mailing lists administration"
msgstr "Administration des mailing listes"
#: com/templates/com/mailing_admin.jinja #: com/templates/com/mailing_admin.jinja
#, python-format #, python-format
msgid "Moderated by %(user)s" msgid "Moderated by %(user)s"
@ -1578,14 +1600,6 @@ msgstr "Discord AE"
msgid "Dev Team" msgid "Dev Team"
msgstr "Pôle Informatique" msgstr "Pôle Informatique"
#: com/templates/com/news_list.jinja
msgid "Facebook"
msgstr "Facebook"
#: com/templates/com/news_list.jinja
msgid "Instagram"
msgstr "Instagram"
#: com/templates/com/news_list.jinja #: com/templates/com/news_list.jinja
msgid "Birthdays" msgid "Birthdays"
msgstr "Anniversaires" msgstr "Anniversaires"
@ -1599,6 +1613,10 @@ msgstr "%(age)s ans"
msgid "You need to subscribe to access this content" msgid "You need to subscribe to access this content"
msgstr "Vous devez cotiser pour accéder à ce contenu" msgstr "Vous devez cotiser pour accéder à ce contenu"
#: com/templates/com/news_list.jinja
msgid "You cannot access this content"
msgstr "Vous n'avez pas accès à ce contenu"
#: com/templates/com/poster_edit.jinja com/templates/com/poster_list.jinja #: com/templates/com/poster_edit.jinja com/templates/com/poster_list.jinja
msgid "Poster" msgid "Poster"
msgstr "Affiche" msgstr "Affiche"
@ -3299,8 +3317,8 @@ msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
#: core/views/forms.py #: core/views/forms.py
msgid "" msgid ""
"Profile: you need to be visible on the picture, in order to be recognized " "Profile: you need to be visible on the picture, in order to be recognized (e."
"(e.g. by the barmen)" "g. by the barmen)"
msgstr "" msgstr ""
"Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "Photo de profil: vous devez être visible sur la photo afin d'être reconnu "
"(par exemple par les barmen)" "(par exemple par les barmen)"
@ -3906,8 +3924,8 @@ msgstr ""
#: counter/templates/counter/mails/account_dump.jinja #: counter/templates/counter/mails/account_dump.jinja
msgid "If you think this was a mistake, please mail us at ae@utbm.fr." msgid "If you think this was a mistake, please mail us at ae@utbm.fr."
msgstr "" msgstr ""
"Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à " "Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à ae@utbm."
"ae@utbm.fr." "fr."
#: counter/templates/counter/mails/account_dump.jinja #: counter/templates/counter/mails/account_dump.jinja
msgid "" msgid ""