v0.7.8 bugs fixes for pinned messages

This commit is contained in:
2026-03-11 19:38:59 -04:00
parent 08243be745
commit 67bea6c2c3
6 changed files with 82 additions and 92 deletions

View File

@@ -7,7 +7,7 @@ TZ=UTC
# Copy this file to .env and customize # Copy this file to .env and customize
# Image version to run (set by build.sh, or use 'latest') # Image version to run (set by build.sh, or use 'latest')
JAMA_VERSION=0.7.7 JAMA_VERSION=0.7.8
# Default admin credentials (used on FIRST RUN only) # Default admin credentials (used on FIRST RUN only)
ADMIN_NAME=Admin User ADMIN_NAME=Admin User

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-backend", "name": "jama-backend",
"version": "0.7.7", "version": "0.7.8",
"description": "TeamChat backend server", "description": "TeamChat backend server",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -13,7 +13,7 @@
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
set -euo pipefail set -euo pipefail
VERSION="${1:-0.7.7}" VERSION="${1:-0.7.8}"
ACTION="${2:-}" ACTION="${2:-}"
REGISTRY="${REGISTRY:-}" REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama" IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-frontend", "name": "jama-frontend",
"version": "0.7.7", "version": "0.7.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -334,72 +334,46 @@
user-select: text; user-select: text;
} }
/* Triple-dot pin button wrapper */ /* Pinned state highlight for toolbar pin button */
.msg-pin-wrap { .action-pinned {
position: relative; color: var(--primary) !important;
}
/* Mobile bottom-sheet overlay for long-press / right-click pin */
.msg-sheet-overlay {
position: fixed;
inset: 0;
z-index: 500;
background: rgba(0,0,0,0.35);
display: flex; display: flex;
align-items: center; align-items: flex-end;
align-self: flex-end; justify-content: center;
margin-bottom: 2px;
} }
.msg-pin-btn { .msg-sheet {
opacity: 0;
transition: opacity 0.15s;
color: var(--text-tertiary);
padding: 2px 4px;
border-radius: 4px;
}
.msg-pin-btn:hover,
.msg-pin-btn.active {
opacity: 1 !important;
color: var(--text-primary);
background: var(--surface-variant);
}
/* Show pin button on message row hover */
.msg-bubble-wrap:hover .msg-pin-btn,
.msg-pin-btn.active {
opacity: 1;
}
/* On touch devices always show it so users know it's there */
@media (hover: none) {
.msg-pin-btn {
opacity: 0.45;
}
}
/* Message pin/options popup menu */
.msg-options-menu {
position: absolute;
z-index: 200;
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border-radius: 16px 16px 0 0;
border-radius: var(--radius); padding: 8px 0 max(16px, env(safe-area-inset-bottom)) 0;
box-shadow: var(--shadow-md); width: 100%;
padding: 4px 0; max-width: 480px;
min-width: 170px; box-shadow: 0 -4px 24px rgba(0,0,0,0.18);
bottom: calc(100% + 4px);
white-space: nowrap;
} }
.msg-options-menu.options-left { .msg-sheet-handle {
right: 0; width: 36px;
height: 4px;
border-radius: 2px;
background: var(--border);
margin: 0 auto 12px;
} }
.msg-options-menu.options-right { .msg-sheet-btn {
left: 0;
}
.msg-options-menu button {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 14px;
width: 100%; width: 100%;
padding: 8px 14px; padding: 14px 24px;
font-size: 13px; font-size: 15px;
color: var(--text-primary); color: var(--text-primary);
background: none; background: none;
border: none; border: none;
@@ -408,11 +382,20 @@
transition: background var(--transition); transition: background var(--transition);
} }
.msg-options-menu button:hover:not(:disabled) { .msg-sheet-btn:hover:not(:disabled) {
background: var(--surface-variant); background: var(--surface-variant);
} }
.msg-options-menu button:disabled { .msg-sheet-btn:disabled {
opacity: 0.45; opacity: 0.45;
cursor: not-allowed; cursor: not-allowed;
} }
.msg-sheet-cancel {
color: var(--text-secondary);
font-weight: 500;
border-top: 1px solid var(--border);
margin-top: 4px;
justify-content: center;
gap: 0;
}

View File

@@ -216,6 +216,7 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
onTouchStart={handleTouchStart} onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd} onTouchEnd={handleTouchEnd}
onTouchMove={handleTouchEnd} onTouchMove={handleTouchEnd}
onContextMenu={isDirect ? (e => { e.preventDefault(); setShowOptionsMenu(true); }) : undefined}
> >
{/* Actions toolbar — floats above the bubble, aligned to correct side */} {/* Actions toolbar — floats above the bubble, aligned to correct side */}
{!isDeleted && (showActions || showEmojiPicker) && ( {!isDeleted && (showActions || showEmojiPicker) && (
@@ -239,6 +240,16 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
</button> </button>
)} )}
{isDirect && (
<button
className={`btn-icon action-btn${isPinned ? ' action-pinned' : ''}`}
onClick={() => { isPinned ? onUnpin(msg.id) : (pinCount < 5 && onPin(msg.id)); setShowActions(false); }}
disabled={!isPinned && pinCount >= 5}
title={isPinned ? 'Unpin message' : pinCount >= 5 ? 'Pin limit reached (5/5)' : `Pin message (${pinCount + 1}/5)`}
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
</button>
)}
{/* Emoji picker anchored to the toolbar */} {/* Emoji picker anchored to the toolbar */}
{showEmojiPicker && ( {showEmojiPicker && (
<div <div
@@ -275,38 +286,34 @@ export default function Message({ message: msg, prevMessage, currentUser, onRepl
<span className="msg-time">{formatTime(msg.created_at)}</span> <span className="msg-time">{formatTime(msg.created_at)}</span>
{/* Triple-dot pin button — sits outside the hover toolbar so mouse-leave doesn't race */} {/* Mobile long-press / right-click bottom sheet for pin */}
{isDirect && ( {isDirect && showOptionsMenu && (
<div className="msg-pin-wrap" ref={optionsMenuRef}> <div className="msg-sheet-overlay" onClick={() => setShowOptionsMenu(false)}>
<button <div
className={`btn-icon msg-pin-btn ${showOptionsMenu ? 'active' : ''}`} className="msg-sheet"
onClick={e => { e.stopPropagation(); setShowOptionsMenu(p => !p); }} ref={optionsMenuRef}
title="Message options" onClick={e => e.stopPropagation()}
> >
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2"><circle cx="12" cy="5" r="1.2" fill="currentColor"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><circle cx="12" cy="19" r="1.2" fill="currentColor"/></svg> <div className="msg-sheet-handle" />
</button> {isPinned ? (
{showOptionsMenu && ( <button className="msg-sheet-btn" onClick={() => { onUnpin(msg.id); setShowOptionsMenu(false); }}>
<div <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
className={`msg-options-menu ${isOwn ? 'options-left' : 'options-right'}`} Unpin message
onMouseDown={e => e.stopPropagation()} </button>
> ) : (
{isPinned ? ( <button
<button onClick={() => { onUnpin(msg.id); setShowOptionsMenu(false); }}> className="msg-sheet-btn"
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg> onClick={() => { if (pinCount < 5) { onPin(msg.id); setShowOptionsMenu(false); } }}
Unpin message disabled={pinCount >= 5}
</button> >
) : ( <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg>
<button {pinCount >= 5 ? 'Pin limit reached (5/5)' : `Pin message (${pinCount + 1}/5)`}
onClick={() => { if (pinCount < 5) { onPin(msg.id); setShowOptionsMenu(false); } }} </button>
disabled={pinCount >= 5} )}
title={pinCount >= 5 ? 'Maximum 5 pinned messages reached' : ''} <button className="msg-sheet-btn msg-sheet-cancel" onClick={() => setShowOptionsMenu(false)}>
> Cancel
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M16 2v4l-3 3v7l-4-4-4 4V9L2 6V2h14zm2 0h2v2h-2V2z"/></svg> </button>
{pinCount >= 5 ? 'Pin (5/5)' : `Pin (${pinCount + 1}/5)`} </div>
</button>
)}
</div>
)}
</div> </div>
)} )}
</div> </div>