v0.9.29 Bugs fixes
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
PROJECT_NAME=jama
|
PROJECT_NAME=jama
|
||||||
|
|
||||||
# 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.9.28
|
JAMA_VERSION=0.9.29
|
||||||
|
|
||||||
# App port — the host port Docker maps to the container
|
# App port — the host port Docker maps to the container
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jama-backend",
|
"name": "jama-backend",
|
||||||
"version": "0.9.28",
|
"version": "0.9.29",
|
||||||
"description": "TeamChat backend server",
|
"description": "TeamChat backend server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -18,11 +18,17 @@ function postSysMsg(db, groupId, userId, content) {
|
|||||||
if (msg) { msg.reactions = []; io.to(`group:${groupId}`).emit('message:new', msg); }
|
if (msg) { msg.reactions = []; io.to(`group:${groupId}`).emit('message:new', msg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUserToDmGroup(db, dmGroupId, userId, actorId) {
|
// Add user silently (no system message) — used during initial group creation
|
||||||
|
function addUserToDmGroupSilent(db, dmGroupId, userId) {
|
||||||
db.prepare("INSERT OR IGNORE INTO group_members (group_id, user_id, joined_at) VALUES (?, ?, datetime('now'))").run(dmGroupId, userId);
|
db.prepare("INSERT OR IGNORE INTO group_members (group_id, user_id, joined_at) VALUES (?, ?, datetime('now'))").run(dmGroupId, userId);
|
||||||
io.in(`user:${userId}`).socketsJoin(`group:${dmGroupId}`);
|
io.in(`user:${userId}`).socketsJoin(`group:${dmGroupId}`);
|
||||||
const dmGroup = db.prepare('SELECT * FROM groups WHERE id = ?').get(dmGroupId);
|
const dmGroup = db.prepare('SELECT * FROM groups WHERE id = ?').get(dmGroupId);
|
||||||
io.to(`user:${userId}`).emit('group:new', { group: dmGroup });
|
io.to(`user:${userId}`).emit('group:new', { group: dmGroup });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user with system message — used when adding to existing group
|
||||||
|
function addUserToDmGroup(db, dmGroupId, userId, actorId) {
|
||||||
|
addUserToDmGroupSilent(db, dmGroupId, userId);
|
||||||
const u = db.prepare('SELECT name, display_name FROM users WHERE id = ?').get(userId);
|
const u = db.prepare('SELECT name, display_name FROM users WHERE id = ?').get(userId);
|
||||||
postSysMsg(db, dmGroupId, actorId, `${u?.display_name || u?.name || 'A user'} has joined the conversation.`);
|
postSysMsg(db, dmGroupId, actorId, `${u?.display_name || u?.name || 'A user'} has joined the conversation.`);
|
||||||
}
|
}
|
||||||
@@ -90,11 +96,9 @@ router.post('/multigroup', authMiddleware, adminMiddleware, (req, res) => {
|
|||||||
for (const uid of uids) {
|
for (const uid of uids) {
|
||||||
if (!addedUsers.has(uid)) {
|
if (!addedUsers.has(uid)) {
|
||||||
addedUsers.add(uid);
|
addedUsers.add(uid);
|
||||||
addUserToDmGroup(db, dmGroupId, uid, req.user.id);
|
addUserToDmGroupSilent(db, dmGroupId, uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ug = db.prepare('SELECT name FROM user_groups WHERE id = ?').get(ugId);
|
|
||||||
if (ug) postSysMsg(db, dmGroupId, req.user.id, `Group "${ug.name}" has been added to this conversation.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dm = db.prepare('SELECT * FROM multi_group_dms WHERE id = ?').get(mgId);
|
const dm = db.prepare('SELECT * FROM multi_group_dms WHERE id = ?').get(mgId);
|
||||||
@@ -199,7 +203,7 @@ router.post('/', authMiddleware, adminMiddleware, (req, res) => {
|
|||||||
const validIds = Array.isArray(memberIds) ? memberIds.map(Number).filter(Boolean) : [];
|
const validIds = Array.isArray(memberIds) ? memberIds.map(Number).filter(Boolean) : [];
|
||||||
for (const uid of validIds) {
|
for (const uid of validIds) {
|
||||||
db.prepare("INSERT OR IGNORE INTO user_group_members (user_group_id, user_id) VALUES (?, ?)").run(ugId, uid);
|
db.prepare("INSERT OR IGNORE INTO user_group_members (user_group_id, user_id) VALUES (?, ?)").run(ugId, uid);
|
||||||
addUserToDmGroup(db, dmGroupId, uid, req.user.id);
|
addUserToDmGroupSilent(db, dmGroupId, uid);
|
||||||
}
|
}
|
||||||
const group = db.prepare('SELECT * FROM user_groups WHERE id = ?').get(ugId);
|
const group = db.prepare('SELECT * FROM user_groups WHERE id = ?').get(ugId);
|
||||||
res.json({ group });
|
res.json({ group });
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${1:-0.9.28}"
|
VERSION="${1:-0.9.29}"
|
||||||
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.9.28",
|
"version": "0.9.29",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ function AllGroupsTab({ allUsers, onRefresh }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Direct Messages tab ───────────────────────────────────────────────────────
|
// ── Direct Messages tab ───────────────────────────────────────────────────────
|
||||||
function DirectMessagesTab({ allUserGroups, onRefresh }) {
|
function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [dms, setDms] = useState([]);
|
const [dms, setDms] = useState([]);
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
@@ -203,8 +203,9 @@ function DirectMessagesTab({ allUserGroups, onRefresh }) {
|
|||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
|
|
||||||
const load = useCallback(() =>
|
const load = useCallback(() =>
|
||||||
api.getMultiGroupDms().then(({ dms }) => setDms(dms)).catch(() => {}), []);
|
api.getMultiGroupDms().then(({ dms }) => setDms(dms || [])).catch(e => console.error('multigroup load error:', e)), []);
|
||||||
useEffect(() => { load(); }, [load]);
|
// Reload whenever parent refreshes (e.g. after user group changes that affect membership)
|
||||||
|
useEffect(() => { load(); }, [load, refreshKey]);
|
||||||
|
|
||||||
const clearSelection = () => {
|
const clearSelection = () => {
|
||||||
setSelected(null); setDmName(''); setGroupIds(new Set()); setSavedGroupIds(new Set()); setShowDelete(false);
|
setSelected(null); setDmName(''); setGroupIds(new Set()); setSavedGroupIds(new Set()); setShowDelete(false);
|
||||||
@@ -355,7 +356,7 @@ export default function GroupManagerModal({ onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
|
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
|
||||||
{tab === 'all' && <AllGroupsTab allUsers={allUsers} onRefresh={onRefresh} />}
|
{tab === 'all' && <AllGroupsTab allUsers={allUsers} onRefresh={onRefresh} />}
|
||||||
{tab === 'dm' && <DirectMessagesTab allUserGroups={allUserGroups} onRefresh={onRefresh} />}
|
{tab === 'dm' && <DirectMessagesTab allUserGroups={allUserGroups} onRefresh={onRefresh} refreshKey={refreshKey} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,7 +53,18 @@ export default function NavDrawer({ open, onClose, onMessages, onGroupManager, o
|
|||||||
<div className={`nav-drawer-backdrop${open ? ' open' : ''}`} onClick={onClose} />
|
<div className={`nav-drawer-backdrop${open ? ' open' : ''}`} onClick={onClose} />
|
||||||
{/* Drawer */}
|
{/* Drawer */}
|
||||||
<div ref={drawerRef} className={`nav-drawer${open ? ' open' : ''}`}>
|
<div ref={drawerRef} className={`nav-drawer${open ? ' open' : ''}`}>
|
||||||
<div className="nav-drawer-section-label">Menu</div>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||||
|
<div className="nav-drawer-section-label" style={{ margin: 0, padding: 0 }}>Menu</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-secondary)', padding: 4, borderRadius: 6, display: 'flex', alignItems: 'center' }}
|
||||||
|
aria-label="Close menu"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{item(NAV_ICON.messages, 'Messages', onMessages)}
|
{item(NAV_ICON.messages, 'Messages', onMessages)}
|
||||||
{!isMobile && item(NAV_ICON.schedules, 'Schedules', () => {}, true)}
|
{!isMobile && item(NAV_ICON.schedules, 'Schedules', () => {}, true)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user