mirror of
https://github.com/ae-utbm/sith.git
synced 2025-02-27 01:47:14 +00:00
Add a "see more" button on news dates list
This commit is contained in:
parent
2def57d82c
commit
71b3588577
@ -0,0 +1,67 @@
|
||||
import { type NewsDateSchema, newsFetchNewsDates } from "#openapi";
|
||||
|
||||
interface ParsedNewsDateSchema extends Omit<NewsDateSchema, "start_date" | "end_date"> {
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
start_date: Date;
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
end_date: Date;
|
||||
}
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("upcomingNewsLoader", (startDate: Date) => ({
|
||||
startDate: startDate,
|
||||
currentPage: 1,
|
||||
pageSize: 6,
|
||||
hasNext: true,
|
||||
loading: false,
|
||||
newsDates: [] as NewsDateSchema[],
|
||||
|
||||
async loadMore() {
|
||||
this.loading = true;
|
||||
const response = await newsFetchNewsDates({
|
||||
query: {
|
||||
after: this.startDate.toISOString(),
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
text_format: "html",
|
||||
page: this.currentPage,
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
page_size: this.pageSize,
|
||||
},
|
||||
});
|
||||
if (response.response.status === 404) {
|
||||
this.hasNext = false;
|
||||
} else if (response.data.next === null) {
|
||||
this.newsDates.push(...response.data.results);
|
||||
this.hasNext = false;
|
||||
} else {
|
||||
this.newsDates.push(...response.data.results);
|
||||
this.currentPage += 1;
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
groupedDates(): Record<string, NewsDateSchema[]> {
|
||||
return this.newsDates
|
||||
.map(
|
||||
(date: NewsDateSchema): ParsedNewsDateSchema => ({
|
||||
...date,
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
start_date: new Date(date.start_date),
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
end_date: new Date(date.end_date),
|
||||
}),
|
||||
)
|
||||
.reduce(
|
||||
(acc: Record<string, ParsedNewsDateSchema[]>, date: ParsedNewsDateSchema) => {
|
||||
const key = date.start_date.toDateString();
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(date);
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
},
|
||||
}));
|
||||
});
|
@ -56,6 +56,13 @@
|
||||
#upcoming-events {
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
|
||||
#load-more-news-button {
|
||||
text-align: center;
|
||||
button {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* LINKS/BIRTHDAYS */
|
||||
|
@ -16,6 +16,7 @@
|
||||
{% block additional_js %}
|
||||
<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>
|
||||
<script type="module" src={{ static("bundled/com/components/upcoming-news-loader-index.ts") }}></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -38,69 +39,140 @@
|
||||
<br>
|
||||
{% endif %}
|
||||
<section id="upcoming-events">
|
||||
{% for day, dates_group in news_dates %}
|
||||
<div class="news_events_group">
|
||||
<div class="news_events_group_date">
|
||||
<div>
|
||||
<div>{{ day|date('D') }}</div>
|
||||
<div class="day">{{ day|date('d') }}</div>
|
||||
<div>{{ day|date('b') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="news_events_group_items">
|
||||
{% for date in dates_group %}
|
||||
<article
|
||||
class="news_event"
|
||||
{%- if not date.news.is_moderated -%}
|
||||
x-data="{newsState: AlertState.PENDING}"
|
||||
{%- endif -%}
|
||||
>
|
||||
{% if not date.news.is_moderated %}
|
||||
{# if a non moderated news is in the object list,
|
||||
the logged user is either an admin or the news author #}
|
||||
{{ news_moderation_alert(date.news, user, "newsState") }}
|
||||
{% endif %}
|
||||
<div
|
||||
{% if not date.news.is_moderated -%}
|
||||
x-show="newsState !== AlertState.DELETED"
|
||||
{%- endif -%}
|
||||
>
|
||||
<header class="row gap">
|
||||
{% if date.news.club.logo %}
|
||||
<img src="{{ date.news.club.logo.url }}" alt="{{ date.news.club }}"/>
|
||||
{% else %}
|
||||
<img src="{{ static("com/img/news.png") }}" alt="{{ date.news.club }}"/>
|
||||
{% endif %}
|
||||
<div class="header_content">
|
||||
<h4>
|
||||
<a href="{{ url('com:news_detail', news_id=date.news_id) }}">
|
||||
{{ date.news.title }}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="{{ date.news.club.get_absolute_url() }}">{{ date.news.club }}</a>
|
||||
<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>
|
||||
</header>
|
||||
<div class="news_content markdown">
|
||||
{{ date.news.summary|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if not news_dates %}
|
||||
<div class="news_empty">
|
||||
<em>{% trans %}Nothing to come...{% endtrans %}</em>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for day, dates_group in news_dates %}
|
||||
<div class="news_events_group">
|
||||
<div class="news_events_group_date">
|
||||
<div>
|
||||
<div>{{ day|date('D') }}</div>
|
||||
<div class="day">{{ day|date('d') }}</div>
|
||||
<div>{{ day|date('b') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="news_events_group_items">
|
||||
{% for date in dates_group %}
|
||||
<article
|
||||
class="news_event"
|
||||
{%- if not date.news.is_moderated -%}
|
||||
x-data="{newsState: AlertState.PENDING}"
|
||||
{%- endif -%}
|
||||
>
|
||||
{% if not date.news.is_moderated %}
|
||||
{# if a non moderated news is in the object list,
|
||||
the logged user is either an admin or the news author #}
|
||||
{{ news_moderation_alert(date.news, user, "newsState") }}
|
||||
{% endif %}
|
||||
<div
|
||||
{% if not date.news.is_moderated -%}
|
||||
x-show="newsState !== AlertState.DELETED"
|
||||
{%- endif -%}
|
||||
>
|
||||
<header class="row gap">
|
||||
{% if date.news.club.logo %}
|
||||
<img src="{{ date.news.club.logo.url }}" alt="{{ date.news.club }}"/>
|
||||
{% else %}
|
||||
<img src="{{ static("com/img/news.png") }}" alt="{{ date.news.club }}"/>
|
||||
{% endif %}
|
||||
<div class="header_content">
|
||||
<h4>
|
||||
<a href="{{ url('com:news_detail', news_id=date.news_id) }}">
|
||||
{{ date.news.title }}
|
||||
</a>
|
||||
</h4>
|
||||
<a href="{{ date.news.club.get_absolute_url() }}">{{ date.news.club }}</a>
|
||||
<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>
|
||||
</header>
|
||||
<div class="news_content markdown">
|
||||
{{ date.news.summary|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div x-data="upcomingNewsLoader(new Date('{{ last_day + timedelta(days=1) }}'))">
|
||||
<template x-for="newsList in Object.values(groupedDates())">
|
||||
<div class="news_events_group">
|
||||
<div class="news_events_group_date">
|
||||
<div x-data="{day: newsList[0].start_date}">
|
||||
<div x-text="day.toLocaleString('{{ get_language() }}', { weekday: 'short' }).substring(0, 3)"></div>
|
||||
<div
|
||||
class="day"
|
||||
x-text="day.toLocaleString('{{ get_language() }}', { day: 'numeric' })"
|
||||
></div>
|
||||
<div x-text="day.toLocaleString('{{ get_language() }}', { month: 'short' }).substring(0, 3)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="news_events_group_items">
|
||||
<template x-for="newsDate in newsList" :key="newsDate.id">
|
||||
<article
|
||||
class="news_event"
|
||||
x-data="{ newsState: newsDate.news.is_moderated ? AlertState.MODERATED : AlertState.PENDING }"
|
||||
>
|
||||
<template x-if="!newsDate.news.is_moderated">
|
||||
{{ news_moderation_alert("newsDate.news.id", user, "newsState") }}
|
||||
</template>
|
||||
<div x-show="newsState !== AlertState.DELETED">
|
||||
<header class="row gap">
|
||||
<img
|
||||
:src="newsDate.news.club.logo || '{{ static("com/img/news.png") }}'"
|
||||
:alt="newsDate.news.club.name"
|
||||
/>
|
||||
<div class="header_content">
|
||||
<h4>
|
||||
<a :href="newsDate.news.url" x-text="newsDate.news.title"></a>
|
||||
</h4>
|
||||
<a :href="newsDate.news.club.url" x-text="newsDate.news.club.name"></a>
|
||||
<div class="news_date">
|
||||
<time
|
||||
:datetime="newsDate.start_date.toISOString()"
|
||||
x-text="`${newsDate.start_date.getHours()}:${newsDate.start_date.getMinutes()}`"
|
||||
></time> -
|
||||
<time
|
||||
:datetime="newsDate.end_date.toISOString()"
|
||||
x-text="`${newsDate.end_date.getHours()}:${newsDate.end_date.getMinutes()}`"
|
||||
></time>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{# The API returns a summary in html.
|
||||
It's generated from our markdown subset, so it should be safe #}
|
||||
<div class="news_content markdown" x-html="newsDate.news.summary"></div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div id="load-more-news-button" :aria-busy="loading">
|
||||
<button class="btn btn-grey" x-show="!loading && hasNext" @click="loadMore()">
|
||||
{% trans %}See more{% endtrans %} <i class="fa fa-arrow-down"></i>
|
||||
</button>
|
||||
<p x-show="!loading && !hasNext">
|
||||
<em>
|
||||
{% trans trimmed %}
|
||||
It was too short.
|
||||
You already reached the end of the upcoming events list.
|
||||
{% endtrans %}
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<h3>
|
||||
|
26
com/views.py
26
com/views.py
@ -253,33 +253,41 @@ class NewsListView(TemplateView):
|
||||
key=lambda u: u.date_of_birth.year,
|
||||
)
|
||||
|
||||
def get_news_dates(self):
|
||||
"""Return the event dates to display.
|
||||
def get_last_day(self) -> date:
|
||||
"""Get the last day when news will be displayed
|
||||
|
||||
The selected events are the ones that happens in the next 3 days
|
||||
where something happen.
|
||||
The returned day is the third one where something happen.
|
||||
For example, if there are 6 events : A on 15/03, B and C on 17/03,
|
||||
D on 20/03, E on 21/03 and F on 22/03 ; then the displayed dates
|
||||
will be A, B, C and D.
|
||||
D on 20/03, E on 21/03 and F on 22/03 ;
|
||||
then the result is 20/03.
|
||||
"""
|
||||
last_date: date = list(
|
||||
return list(
|
||||
NewsDate.objects.filter(end_date__gt=now())
|
||||
.order_by("start_date")
|
||||
.values_list("start_date__date", flat=True)
|
||||
.distinct()[:4]
|
||||
)[-1]
|
||||
|
||||
def get_news_dates(self, until: date):
|
||||
"""Return the event dates to display.
|
||||
|
||||
The selected events are the ones that happens between
|
||||
right now and the given day (included).
|
||||
"""
|
||||
return itertools.groupby(
|
||||
NewsDate.objects.viewable_by(self.request.user)
|
||||
.filter(end_date__gt=now(), start_date__date__lte=last_date)
|
||||
.filter(end_date__gt=now(), start_date__date__lte=until)
|
||||
.order_by("start_date")
|
||||
.select_related("news", "news__club"),
|
||||
key=lambda d: d.start_date.date(),
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
last_day = self.get_last_day()
|
||||
return super().get_context_data(**kwargs) | {
|
||||
"news_dates": self.get_news_dates(),
|
||||
"news_dates": self.get_news_dates(until=last_day),
|
||||
"birthdays": self.get_birthdays(),
|
||||
"last_day": last_day,
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-18 15:03+0100\n"
|
||||
"POT-Creation-Date: 2025-02-19 19:12+0100\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -1429,11 +1429,11 @@ msgstr ""
|
||||
#: com/templates/com/macros.jinja
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This event will take place every week for %(nb)s weeks. If you moderate or "
|
||||
"This event will take place every week for %%s weeks. If you moderate or "
|
||||
"delete this event, it will also be moderated (or deleted) for the following "
|
||||
"weeks."
|
||||
msgstr ""
|
||||
"Cet événement se déroulera chaque semaine pendant %(nb)s semaines. Si vous "
|
||||
"Cet événement se déroulera chaque semaine pendant %%s semaines. Si vous "
|
||||
"modérez ou supprimez cet événement, il sera également modéré (ou supprimé) "
|
||||
"pour les semaines suivantes."
|
||||
|
||||
@ -1578,6 +1578,18 @@ msgstr "Administrer les news"
|
||||
msgid "Nothing to come..."
|
||||
msgstr "Rien à venir..."
|
||||
|
||||
#: com/templates/com/news_list.jinja
|
||||
msgid "See more"
|
||||
msgstr "Voir plus"
|
||||
|
||||
#: com/templates/com/news_list.jinja
|
||||
msgid ""
|
||||
"It was too short. You already reached the end of the upcoming events "
|
||||
"list."
|
||||
msgstr ""
|
||||
"C'était trop court. Vous êtes déjà arrivés à la fin de la liste des "
|
||||
"événements à venir."
|
||||
|
||||
#: com/templates/com/news_list.jinja
|
||||
msgid "All coming events"
|
||||
msgstr "Tous les événements à venir"
|
||||
|
@ -171,6 +171,7 @@ TEMPLATES = [
|
||||
"timezone": "django.utils.timezone",
|
||||
"get_sith": "com.views.sith",
|
||||
"get_language": "django.utils.translation.get_language",
|
||||
"timedelta": "datetime.timedelta",
|
||||
},
|
||||
"bytecode_cache": {
|
||||
"name": "default",
|
||||
|
Loading…
x
Reference in New Issue
Block a user