v0.7.8 bugs fixes for pinned messages
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user