Volver a la wiki

Chat del equipo — Avisos audio+badge, maximizar, paste imágenes y Jitsi (PR#58)

Chat del equipo — Mejoras s77

Cuatro mejoras del widget TeamChat del dashboard interno (DesktopA), implementadas en PR#58 por petición de Edu. El widget vive en src/components/widgets/TeamChat.tsx y se embebe en la variante de layout src/components/variants/desktop/DesktopA.tsx.


1 · Avisos audio + badge de mensajes no leídos

Cuando llega un mensaje de otro miembro del equipo y el tab está oculto (document.visibilityState !== 'visible'), el sistema dispara tres tipos de aviso coordinados:

CanalMecanismo
AudioBeep de dos tonos «ding» (880 Hz → 1100 Hz, ~320 ms) vía Web Audio API. Solo si el tab está oculto.
Título de pestaña(N) Workspace — prefijo con el contador de no leídos. Se resetea al recuperar el foco.
FaviconPunto rojo dibujado en canvas sobre el favicon original. Fallback a círculo rojo entero si hay error CORS.
Chip internoChip rojo «N sin leer» visible dentro del propio widget mientras unreadCount > 0.

La lógica reside en src/lib/chatNotify.ts (funciones playChatBeep, setTabBadge, setFaviconBadge).

Persistencia: el ID del último mensaje visto (lastSeenId) se guarda en localStorage bajo la clave workspace:chat:lastSeenId. Se actualiza al recuperar foco o visibilidad, y se carga al montar el componente para distinguir mensajes realmente nuevos de los que ya se habían leído en sesiones anteriores.

El primer fetch no dispara aviso: initialFetchDoneRef asegura que al cargar la página no suene el beep por mensajes históricos.


2 · Maximizar widget

El bento del chat soporta un estado maximized que lo saca del grid del dashboard mediante position: fixed:

.team-chat-bento.maximized {
  position: fixed;
  top: 76px;
  right: 24px;
  bottom: 24px;
  width: clamp(360px, 34vw, 520px);
  z-index: 60;
  box-shadow: 0 20px 60px rgba(0,0,0,0.45);
  max-height: calc(100vh - 100px);
}

3 · Pegar capturas con Ctrl+V

El textarea del chat acepta imágenes del portapapeles. Flujo:

onPaste → detecta image/* en clipboardData.items
  → e.preventDefault()
  → POST /api/chat/upload-image (multipart, field: "file")
  → responde { filename, url, content_type }
  → inserta marcador [abrir imagen:<url>] en el draft

En el render, CHAT_IMG_REGEX detecta el marcador y lo reemplaza con un link <abrir imagen> (cyan, JetBrains Mono, target="_blank"). Las URLs sueltas siguen siendo autolinkificadas por URL_REGEX, y ambos tokenizadores coexisten sin duplicar matches.

Restricciones del endpoint de upload:


4 · Reunión de voz/vídeo con Jitsi Meet

Botón «Reunión» en la toolbar interna del widget abre JitsiMeetingModal, un portal React renderizado en document.body.

ParámetroValor
Servidormeet.jit.si (infraestructura pública Jitsi, gratuita)
SalaCrearackSL-Workspace-EduDaniTxell-Sala (fija, sin cuenta)
Permisos iframecamera; microphone; fullscreen; display-capture; autoplay
CapacidadesVoz + vídeo + screen share + chat interno Jitsi

Decisiones de diseño


Archivos clave

ArchivoRol
src/lib/chatNotify.tsUtilidades audio+badge (playChatBeep, setTabBadge, setFaviconBadge)
src/components/widgets/TeamChat.tsxWidget principal — integra las 4 mejoras
src/components/widgets/JitsiMeetingModal.tsxModal iframe Jitsi (portal React)
src/components/variants/desktop/DesktopA.tsxLayout host — estado chatMaximized
functions/api/chat/upload-image.tsCF Pages Function — POST upload a R2
functions/api/chat-images/[name].tsCF Pages Function — GET stream desde R2
src/styles/globals.css.team-chat-bento.maximized — CSS fixed overlay

Véase también

Subir