14 Commits

Author SHA1 Message Date
dc97c1d67a chore: delete comment 2026-01-25 20:08:43 +01:00
ea2777dee1 feature: new contact page 2026-01-25 20:07:18 +01:00
247efcb058 feature: new post 2026-01-25 20:07:07 +01:00
050fd7dcce feature: forum banner 2026-01-25 20:06:55 +01:00
b451fdfaf2 Merge pull request 'Update README with current status' (#21) from feature/main-UpdateREADME into main
Reviewed-on: OpenBokeron/Web#21
2026-01-23 14:22:13 +01:00
58cb5fb5ee Update README with current status 2026-01-23 14:21:59 +01:00
d4b972cc4e Merge pull request 'feature/main-19-ActivitiesTab' (#20) from feature/main-19-ActivitiesTab into main
Reviewed-on: OpenBokeron/Web#20
Reviewed-by: Pablo Ferreiro <me@pabloferreiro.es>
2026-01-20 20:22:44 +01:00
326e76aac4 Delete files 2026-01-19 09:01:34 +01:00
66027471f6 Limit scope 2026-01-19 09:01:23 +01:00
2d509e352d Delete limit 2026-01-18 21:55:07 +01:00
f6ed158b0f Add Activities tab 2026-01-18 21:49:53 +01:00
404d2cb716 Merge pull request 'Update index.md' (#16) from feature/main-UpdateMainPage into main
Reviewed-on: OpenBokeron/Web#16
2026-01-16 19:40:03 +01:00
9bc8d09525 Update index.md 2026-01-16 19:38:49 +01:00
00b79db0d2 Merge pull request 'Fix typo in title' (#15) from fix/main-FixTypo into main
Reviewed-on: OpenBokeron/Web#15
2026-01-15 21:10:06 +01:00
16 changed files with 832 additions and 29 deletions

View File

@@ -1,11 +1,7 @@
*WIP*
# Website for Open Bokeron
This repo hosts the website for Open Bokeron which is a student organization in University of Málaga whose aim is to promote the use of free(as in freedom) software.
The philosophy with this website for now is to KISS(keep it stupid simple) using only HTML5 & CSS 3.
## Developer preview
You need [Hugo](https://gohugo.io/) installed.
@@ -18,7 +14,7 @@ hugo server
## TODO
- [x] Add our logo on the website, favicon.
- [ ] Add content describing our association and other pages as needed.
- [x] Add content describing our association and other pages as needed.
- [x] Work on SEO (OpenGraph, Twitter meta tags...)
- [x] Multilingual support(English + Spanish)
- [x] Support for RSS (index.xml)

View File

@@ -1,12 +1,377 @@
/* Override colors from CSS library */
:root {
--accent: green;
--accent-hover: darkgreen;
--accent: green;
--accent-hover: darkgreen;
}
/* Custom style for the logo */
img.icon {
max-width: 128px;
max-height: 128px;
margin-top: 10px;
max-width: 128px;
max-height: 128px;
margin-top: 10px;
}
:root {
--activity-bg: transparent;
--activity-panel: rgba(255, 255, 255, 0.03);
--activity-card: rgba(255, 255, 255, 0.04);
--activity-border: rgba(255, 255, 255, 0.08);
--activity-muted: rgba(255, 255, 255, 0.58);
--activity-accent: rgba(255, 255, 255, 0.82);
--activity-accent-soft: rgba(255, 255, 255, 0.1);
--activity-warning: #b9986a;
--activity-tag: rgba(255, 255, 255, 0.06);
}
.activities-shell {
margin-top: 2.5rem;
padding: 0;
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
}
.activities-header {
display: flex;
flex-wrap: wrap;
gap: 2rem;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2.5rem;
}
.activities-kicker {
letter-spacing: 0.14em;
text-transform: uppercase;
font-size: 0.62rem;
color: var(--activity-muted);
margin-bottom: 0.6rem;
}
.activities-title {
margin-top: 0;
margin-bottom: 0.8rem;
font-size: clamp(1.7rem, 2vw + 1.2rem, 2.4rem);
}
.activities-lead {
max-width: 38rem;
color: var(--activity-muted);
}
.activities-actions {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
align-items: center;
}
.activities-actions .button {
background: transparent;
border: 1px solid var(--activity-border);
color: #e7e9ec;
padding: 0.45rem 0.9rem;
border-radius: 999px;
transition: all 0.2s ease;
}
.activities-actions .button:hover {
border-color: rgba(255, 255, 255, 0.22);
color: #f5f7fa;
}
.activities-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.25rem;
}
.activity-card {
border-radius: 12px;
padding: 1.25rem;
background: var(--activity-card);
border: 1px solid var(--activity-border);
position: relative;
overflow: hidden;
min-height: 210px;
display: flex;
flex-direction: column;
gap: 0.8rem;
transition: border-color 0.2s ease;
}
.activity-card:hover {
border-color: rgba(255, 255, 255, 0.18);
}
.activity-card h3 {
margin: 0;
font-size: 1.1rem;
}
.activity-card p {
margin: 0;
color: var(--activity-muted);
font-size: 0.92rem;
}
.activity-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
}
.activity-status {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.7rem;
padding: 0.18rem 0.5rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.8);
}
.activity-status::before {
content: "●";
font-size: 0.6rem;
}
.activity-status.status-closed {
background: rgba(185, 152, 106, 0.25);
color: var(--activity-warning);
}
.activity-status.status-cooking {
background: green;
color: #f5f7fa;
}
.activity-meta {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
font-size: 0.75rem;
color: var(--activity-muted);
}
.activity-tags {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.activity-tags .activity-tag:nth-child(n + 3) {
display: none;
}
.activity-tag {
padding: 0.2rem 0.5rem;
border-radius: 999px;
background: var(--activity-tag);
color: #d5d9e0;
font-size: 0.68rem;
}
.activity-link {
margin-top: auto;
color: #f5f7fa;
font-weight: 600;
text-decoration: none;
}
.activity-link:hover {
text-decoration: underline;
}
.activities-section {
margin-bottom: 2.5rem;
}
.activities-section-head {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
gap: 0.8rem;
margin-bottom: 1.2rem;
}
.activities-section-head h3 {
margin: 0;
font-size: 1.05rem;
}
.activities-section-head p {
margin: 0;
color: var(--activity-muted);
font-size: 0.88rem;
}
.activities-ideas {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.75rem;
}
.activities-idea {
padding: 0.75rem 0.9rem;
border-radius: 10px;
border: 1px solid var(--activity-border);
background: var(--activity-panel);
display: flex;
flex-wrap: wrap;
gap: 0.6rem 1rem;
justify-content: space-between;
align-items: center;
color: #e0e5ea;
font-size: 0.92rem;
}
.activities-idea a {
color: #f5f7fa;
font-weight: 600;
text-decoration: none;
}
.activities-idea a:hover {
text-decoration: underline;
}
.activities-footer {
margin-top: 2.4rem;
padding: 1.4rem 1.6rem;
border-radius: 16px;
background: var(--activity-panel);
border: 1px solid var(--activity-border);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.activities-footer p {
margin: 0;
color: var(--activity-muted);
}
.activities-footer .button {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.22);
color: #f5f7fa;
padding: 0.5rem 1.1rem;
border-radius: 999px;
}
.skeleton {
position: relative;
overflow: hidden;
}
.skeleton::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(110deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08));
animation: shimmer 1.8s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.activities-empty {
grid-column: 1 / -1;
padding: 1.5rem;
text-align: center;
background: var(--activity-panel);
border-radius: 12px;
border: 1px dashed var(--activity-border);
color: var(--activity-muted);
}
.activities-fallback {
margin-top: 1.2rem;
padding: 1rem 1.2rem;
border-radius: 12px;
border: 1px solid var(--activity-border);
color: var(--activity-muted);
font-size: 0.9rem;
}
.activities-fallback a {
color: #f5f7fa;
}
@media (max-width: 720px) {
.activities-shell {
padding: 0;
}
.activities-actions {
width: 100%;
}
.activities-actions .button {
width: 100%;
text-align: center;
}
.activities-idea {
flex-direction: column;
align-items: flex-start;
}
.activities-footer {
flex-direction: column;
align-items: flex-start;
}
}
.forum-banner {
margin: 3rem 0 2.5rem;
padding: 2rem 1.25rem 1.5rem;
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 10px;
}
.forum-banner__content {
max-width: 720px;
margin: 0 auto;
text-align: center;
}
.forum-banner h3 {
padding: 1rem 1rem 1rem;
margin-bottom: 0.25rem;
}
.forum-banner p {
margin-top: 0;
color: #cfcfcf;
}
.forum-banner__button {
display: inline-block;
padding: 0.5rem 1rem;
border-radius: 8px;
text-decoration: none;
font-weight: 700;
background: #1faa00;
color: #000;
}

View File

@@ -0,0 +1,42 @@
---
title: Activities in progress
---
See what we are preparing and the current status of each proposal. Everything lives in our public Gitea board.
<div class="activities-shell" data-activities-root>
<header class="activities-header">
<div class="activities-actions">
<a class="button" href="https://openbokeron.uma.es/gitea/OpenBokeron/-/projects/5" target="_blank" rel="noopener">View board</a>
<a class="button" href="https://openbokeron.uma.es/gitea/OpenBokeron/Actividades/issues" target="_blank" rel="noopener">View issues</a>
</div>
</header>
<section class="activities-section" data-activities-working>
<div class="activities-section-head">
<h3>In development</h3>
</div>
<div class="activities-grid" data-activities-grid>
<article class="activity-card skeleton">
<div class="activity-head">
<span class="activity-status">Loading</span>
</div>
<h3>Loading...</h3>
</article>
</div>
</section>
<section class="activities-section" data-activities-ideas>
<div class="activities-section-head">
<h3>Backlog</h3>
</div>
<ul class="activities-ideas" data-activities-ideas-list>
<li class="activities-idea skeleton">Loading...</li>
</ul>
</section>
<footer class="activities-footer">
<p>Looking for past activities? You will find them in the Projects tab.</p>
<p>Want to propose something new? Drop by the Contact tab, we do not bite.</p>
</footer>
</div>

View File

@@ -6,15 +6,41 @@ title: Contact
If you are at the ETSII of the University of Malaga (UMA), don't hesitate to look for us! OpenBokeron members
are easy to find in the hallways, classrooms and laboratories.
No luck meeting us in person? Don't worry! You can always contact us through email
Havent managed to find us in person? No worries — there are several ways to get in touch and take part in the community.
---
## Ways to participate
#### 💬 Community forum
We have created a forum as the main meeting point for the community: to share ideas, ask questions,
organize activities, or simply introduce yourself.
https://openbokeron.org/flarum
This is the best place to start if youre not sure where to begin.
---
#### 📢 Telegram
We also have a Telegram group where the community has been chatting informally for a long time, in real time.
https://t.me/+-0oloTKiIo00ZmM0
Ideal for quick conversations or keeping up with whats happening.
#### ✉️ Email
If you prefer a more direct or private way of contacting us, you can email us at:
[openbokeron@geeklab.es](mailto:openbokeron@geeklab.es)
We'd be happy to answer your questions, get suggestions, or just chat about free software!
---
### Our social media
Don't miss the latest news, events and projects from Open Bokeron on Twitter!
Follow us on our account [@OpenBokeron](https://twitter.com/OpenBokeron)
#### 🐦 Social media
We share news, events, and updates on our X (formerly Twitter) account:
Here we share real-time updates, curiosities and everything related to the world of free software.
https://twitter.com/OpenBokeron
You can also talk to our community using Telegram with [this link](https://t.me/+-0oloTKiIo00ZmM0)
---
Well be happy to answer your questions, receive suggestions, or just chat about free software!

19
content/en/posts/forum.md Normal file
View File

@@ -0,0 +1,19 @@
---
title: "We have launched the Open Bokeron community forum"
description: "We have created a forum to make participation easier and bring the community to life."
date: 2026-01-25T12:00:00+01:00
---
We have launched a **community forum** for Open Bokeron to make participation easier and create a more welcoming space for discussion and collaboration.
The forum is intended as an informal meeting point where anyone can:
- Introduce themselves and meet other members
- Suggest workshops, talks, or projects
- Ask for help or share knowledge
- Discuss free software, privacy, and technology in general
👉 **You can access the forum here:**
https://openbokeron.org/flarum
If youre not sure where to start, just drop by and say hello.
Everyone is welcome!

View File

@@ -10,7 +10,7 @@ title: Bienvenido
¿Te gustaría aprender, compartir conocimientos y participar en proyectos abiertos?
Bienvenido a Open Bokeron 👋
¡Bienvenido a Open Bokeron! 👋
Somos una asociación de estudiantes de la ETSI Informática de la Universidad de Málaga dedicada a promover el software libre y el código abierto, fomentando una tecnología más abierta, accesible y colaborativa.

View File

@@ -0,0 +1,42 @@
---
title: Actividades en marcha
---
Descubre lo que estamos preparando y en qué punto está cada propuesta. Todo esto se coordina en nuestro tablero público de Gitea.
<div class="activities-shell" data-activities-root>
<header class="activities-header">
<div class="activities-actions">
<a class="button" href="https://openbokeron.uma.es/gitea/OpenBokeron/-/projects/5" target="_blank" rel="noopener">Ver tablero</a>
<a class="button" href="https://openbokeron.uma.es/gitea/OpenBokeron/Actividades/issues" target="_blank" rel="noopener">Ver issues</a>
</div>
</header>
<section class="activities-section" data-activities-working>
<div class="activities-section-head">
<h3>En desarrollo</h3>
</div>
<div class="activities-grid" data-activities-grid>
<article class="activity-card skeleton">
<div class="activity-head">
<span class="activity-status">Cargando</span>
</div>
<h3>Cargando...</h3>
</article>
</div>
</section>
<section class="activities-section" data-activities-ideas>
<div class="activities-section-head">
<h3>En el tintero</h3>
</div>
<ul class="activities-ideas" data-activities-ideas-list>
<li class="activities-idea skeleton">Cargando...</li>
</ul>
</section>
<footer class="activities-footer">
<p> ¿Buscas las Actividades ya realizadas? Las tienes en la pestaña de Proyectos ;) </p>
<p>¿Quieres proponer algo nuevo? Pásate por la pestaña Contacto, no mordemos.</p>
</footer>
</div>

View File

@@ -7,18 +7,42 @@ Si te encuentras en la Escuela Técnica Superior de Ingeniería Informática y T
(UMA), ¡no dudes en buscarnos! Los miembros de Open Bokeron
son fáciles de encontrar por los pasillos, aulas y laboratorios.
¿No has tenido ocasión de encontrarnos en persona? No pasa nada: hay varias formas de ponerte en contacto y participar en la comunidad.
¿No has tenido suerte encontrándonos en persona? ¡No te preocupes! Siempre puedes ponerte en contacto con
nosotros a través de nuestro correo electrónico [openbokeron@geeklab.es](mailto:openbokeron@geeklab.es)
---
¡Estaremos encantados de responder a tus preguntas, recibir tus sugerencias o simplemente charlar sobre software libre!
## Formas de participar
### Nuestras redes sociales
¡No te pierdas las últimas noticias, eventos y proyectos de Open Bokeron en Twitter! Síguenos en nuestra cuenta
oficial [@OpenBokeron](https://twitter.com/OpenBokeron)
#### 💬 Foro de la comunidad
Hemos creado un foro como punto de encuentro principal para la comunidad: proponer ideas, hacer preguntas o simplemente presentarte.
Aquí compartimos actualizaciones en tiempo real, curiosidades y todo lo relacionado con el mundo del software libre.
https://openbokeron.org/flarum
También puedes charlar con nosotros y toda la comunidad open source usando Telegram con [este enlace](https://t.me/+-0oloTKiIo00ZmM0)
Es el mejor sitio para empezar si no sabes por dónde.
¡Os esperamos!
---
#### 📢 Telegram
Disponemos de un grupo de Telegram donde la comunidad lleva tiempo charlando de forma más informal y en tiempo real.
https://t.me/+-0oloTKiIo00ZmM0
Ideal para conversaciones rápidas o estar al tanto de lo que va surgiendo.
---
#### ✉️ Correo electrónico
Si prefieres un contacto más directo o privado, puedes escribirnos a:
[openbokeron@geeklab.es](mailto:openbokeron@geeklab.es)
---
#### 🐦 Redes sociales
Compartimos noticias, eventos y actualizaciones en nuestra cuenta de Twitter:
https://twitter.com/OpenBokeron
---
¡Estaremos encantados de responder a tus preguntas, recibir sugerencias o simplemente charlar sobre software libre!

19
content/es/posts/foro.md Normal file
View File

@@ -0,0 +1,19 @@
---
title: "Abrimos el foro de Open Bokeron"
description: "Hemos creado un foro para facilitar la participación y dar vida a la comunidad."
date: 2026-01-25T12:00:00+01:00
---
En Open Bokeron hemos puesto en marcha un **foro de la comunidad** con el objetivo de facilitar la participación y crear un espacio más cercano donde charlar, proponer ideas y organizar actividades.
El foro está pensado como un punto de encuentro informal, donde cualquiera puede:
- Presentarse y conocer a otras personas de la asociación
- Proponer talleres, charlas o proyectos
- Pedir ayuda o resolver dudas
- Comentar sobre software libre, privacidad y tecnología en general
👉 **Puedes acceder al foro aquí:**
https://openbokeron.org/flarum
Si te apetece participar pero no sabes por dónde empezar, pásate por el foro y saluda.
¡Toda aportación es bienvenida!

View File

@@ -34,6 +34,12 @@ buildFuture = true
url = "/en/projects"
weight = 30
[[languages.en.menu.main]]
identifier = "activities"
name = "Activities"
url = "/en/activities"
weight = 35
[[languages.en.menu.main]]
identifier = "contact"
name = "Contact"
@@ -65,6 +71,12 @@ buildFuture = true
url = "/es/projects"
weight = 30
[[languages.es.menu.main]]
identifier = "activities"
name = "Actividades"
url = "/es/activities"
weight = 35
[[languages.es.menu.main]]
identifier = "contact"
name = "Contacto"
@@ -76,3 +88,8 @@ buildFuture = true
[params]
[params.social]
twitter = 'OpenBokeron'
[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true

View File

@@ -4,3 +4,6 @@ motto = "Free your source, free your mind"
# Project
finished = "Finished"
unfinished = "Ongoing"
forumTitle = "New: Community forum"
forumDesc = "We have created a forum to make participation easier. Come say hello!"
forumBtn = "Go to the forum"

View File

@@ -2,3 +2,6 @@ motto = "Libera tu código, libera tu mente"
finished = "Finalizado"
unfinished = "En progreso"
forumTitle = "Nuevo: Foro de la comunidad"
forumDesc = "Hemos creado un foro para que sea más fácil participar. ¡Pásate y saluda!"
forumBtn = "Entrar al foro"

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}"
dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
{{ partial "head.html" . -}}
@@ -15,6 +16,9 @@
<footer>
{{ partial "footer.html" . -}}
</footer>
{{ if eq .Section "activities" }}
<script src="/assets/js/activities.js" defer></script>
{{ end }}
</body>
</html>

View File

@@ -1,3 +1,4 @@
{{ define "main" }}
{{ .Content }}
{{ partial "forum-banner.html" . }}
{{ .Content }}
{{ end }}

View File

@@ -0,0 +1,7 @@
<section class="forum-banner">
<div class="forum-banner__content">
<h3>{{ i18n "forumTitle" }}</h3>
<p>{{ i18n "forumDesc" }}</p>
<a class="forum-banner__button" href="/flarum">{{ i18n "forumBtn" }}</a>
</div>
</section>

View File

@@ -0,0 +1,235 @@
const ACTIVITIES_CONFIG = {
apiBase: "https://openbokeron.uma.es/gitea/api/v1",
repo: "OpenBokeron/Actividades",
state: "open",
};
const ACTIVITY_LABELS = {
cooking: "cocinando",
};
const ACTIVITY_BADGES = {
es: {
cooking: "Cocinando",
},
en: {
cooking: "Cooking",
},
};
const ACTIVITY_BADGE_STYLE = {
cooking: "status-cooking",
};
const ACTIVITY_CTA = {
es: "Ver en Gitea",
en: "View on Gitea",
};
const ACTIVITY_EMPTY = {
es: "Ahora mismo no hay actividades abiertas. Vuelve pronto o mira el tablero completo.",
en: "There are no open activities right now. Come back soon or check the full board.",
};
const FALLBACK_TEXT = {
es: "Oops, la hemos liado al querer cargar el tablero. Puedes verlo directamente en Gitea:",
en: "Oops, we messed up while trying to load the board. You can still open it in Gitea:",
};
const FALLBACK_LINK = {
es: "Abrir tablero",
en: "Open board",
};
const LANGUAGE_LABELS = {
es: {
milestone: "Hito",
comments: "comentarios",
updated: "Actualizado",
noCooking: "No hay actividades en desarrollo. Mira el tablero completo.",
noIdeas: "No hay ideas nuevas ahora mismo.",
},
en: {
milestone: "Milestone",
comments: "comments",
updated: "Updated",
noCooking: "No activities in development. Check the full board.",
noIdeas: "There are no new ideas right now.",
},
};
const escapeHtml = (value) => {
if (value === null || value === undefined) {
return "";
}
return String(value).replace(/[&<>"']/g, (char) => {
const map = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};
return map[char] || char;
});
};
const container = document.querySelector("[data-activities-root]");
if (container) {
const grid = container.querySelector("[data-activities-grid]");
const lang = document.documentElement.lang.startsWith("es") ? "es" : "en";
const labels = LANGUAGE_LABELS[lang];
const fetchActivities = async () => {
const url = `${ACTIVITIES_CONFIG.apiBase}/repos/${ACTIVITIES_CONFIG.repo}/issues?state=${ACTIVITIES_CONFIG.state}`;
const response = await fetch(url, {
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
};
const formatDate = (value) => {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
return new Intl.DateTimeFormat(lang, {
year: "numeric",
month: "short",
day: "numeric",
}).format(date);
};
const getLabelNames = (issue) => (issue.labels || []).map((label) => label.name.toLowerCase());
const hasLabel = (issue, label) => getLabelNames(issue).includes(label);
const renderTags = (tags) => {
if (!tags || tags.length === 0) {
return "";
}
return tags
.filter((label) => label.name.toLowerCase() !== ACTIVITY_LABELS.cooking)
.slice(0, 3)
.map((label) => `<span class="activity-tag">${label.name}</span>`)
.join("");
};
const getBadge = (issue) => {
if (hasLabel(issue, ACTIVITY_LABELS.cooking)) {
return {
text: ACTIVITY_BADGES[lang].cooking,
className: ACTIVITY_BADGE_STYLE.cooking,
};
}
return null;
};
const renderActivity = (issue) => {
const tags = renderTags(issue.labels);
const milestone = issue.milestone?.title;
const updated = formatDate(issue.updated_at);
const badge = getBadge(issue);
const comments = issue.comments || 0;
return `
<article class="activity-card">
<div class="activity-head">
${badge ? `<span class="activity-status ${badge.className}">${badge.text}</span>` : ""}
</div>
<h3>${escapeHtml(issue.title)}</h3>
<p>${issue.body ? escapeHtml(issue.body.replace(/\s+/g, " ").slice(0, 140) + "...") : ""}</p>
<div class="activity-meta">
<span>${labels.updated} ${updated}</span>
<span>${comments} ${labels.comments}</span>
<span>#${issue.number}</span>
</div>
${milestone ? `<div class="activity-meta">${labels.milestone}: ${milestone}</div>` : ""}
${tags ? `<div class="activity-tags">${tags}</div>` : ""}
<a class="activity-link" href="${issue.html_url}" target="_blank" rel="noopener">${ACTIVITY_CTA[lang]}</a>
</article>
`;
};
const renderIdeas = (ideas) => {
const ideasList = container.querySelector("[data-activities-ideas-list]");
if (!ideasList) {
return;
}
if (!ideas || ideas.length === 0) {
ideasList.innerHTML = `<li class="activities-idea">${labels.noIdeas}</li>`;
return;
}
ideasList.innerHTML = ideas
.map(
(issue) => `
<li class="activities-idea">
<div>
<strong>${escapeHtml(issue.title)}</strong>
<span class="activity-meta">#${issue.number}</span>
</div>
<a href="${issue.html_url}" target="_blank" rel="noopener">${ACTIVITY_CTA[lang]}</a>
</li>
`
)
.join("");
};
const renderActivities = (issues) => {
if (!issues || issues.length === 0) {
grid.innerHTML = `<div class="activities-empty">${ACTIVITY_EMPTY[lang]}</div>`;
renderIdeas([]);
return;
}
const cooking = issues.filter((issue) => hasLabel(issue, ACTIVITY_LABELS.cooking));
const ideas = issues.filter((issue) => !hasLabel(issue, ACTIVITY_LABELS.cooking));
const cookingTitles = new Set(cooking.map((issue) => issue.title));
const filteredIdeas = ideas.filter((issue) => !cookingTitles.has(issue.title));
if (cooking.length === 0) {
grid.innerHTML = `<div class="activities-empty">${labels.noCooking}</div>`;
} else {
grid.innerHTML = cooking.map(renderActivity).join("");
}
renderIdeas(filteredIdeas);
};
const renderFallback = () => {
grid.innerHTML = `
<div class="activities-empty">
${FALLBACK_TEXT[lang]}
<a href="https://openbokeron.uma.es/gitea/OpenBokeron/-/projects/5" target="_blank" rel="noopener">${FALLBACK_LINK[lang]}</a>
</div>
`;
const ideasList = container.querySelector("[data-activities-ideas-list]");
if (ideasList) {
ideasList.innerHTML = `<li class="activities-idea">${labels.noIdeas}</li>`;
}
};
fetchActivities()
.then((issues) => {
const sorted = [...issues].sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
renderActivities(sorted);
})
.catch(() => {
renderFallback();
});
}