v0.9.14 adjusted emoticon and image view.

This commit is contained in:
2026-03-14 13:52:58 -04:00
parent 2d0214fc10
commit 2ffa6202f1
7 changed files with 50 additions and 20 deletions

View File

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

View File

@@ -1,22 +1,27 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
export default function ImageLightbox({ src, onClose }) {
const overlayRef = useRef(null);
const imgRef = useRef(null);
// Close on Escape
useEffect(() => {
const handler = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
// Prevent body scroll while open
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', handler);
document.body.style.overflow = '';
};
}, [onClose]);
return (
return createPortal(
<div
ref={overlayRef}
onClick={(e) => e.target === overlayRef.current && onClose()}
style={{
position: 'fixed', inset: 0, zIndex: 2000,
position: 'fixed', inset: 0, zIndex: 9999,
background: 'rgba(0,0,0,0.92)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
touchAction: 'pinch-zoom',
@@ -30,8 +35,9 @@ export default function ImageLightbox({ src, onClose }) {
background: 'rgba(255,255,255,0.15)', border: 'none',
borderRadius: '50%', width: 40, height: 40,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', color: 'white', zIndex: 2001,
cursor: 'pointer', color: 'white', zIndex: 10000,
}}
title="Close"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
@@ -47,7 +53,7 @@ export default function ImageLightbox({ src, onClose }) {
background: 'rgba(255,255,255,0.15)', border: 'none',
borderRadius: '50%', width: 40, height: 40,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', color: 'white', zIndex: 2001, textDecoration: 'none',
cursor: 'pointer', color: 'white', zIndex: 10000, textDecoration: 'none',
}}
title="Download"
>
@@ -60,19 +66,20 @@ export default function ImageLightbox({ src, onClose }) {
{/* Image — fit to screen, browser handles pinch-zoom natively */}
<img
ref={imgRef}
src={src}
alt="Full size"
style={{
maxWidth: '95vw',
maxHeight: '95vh',
maxWidth: '92vw',
maxHeight: '92vh',
objectFit: 'contain',
borderRadius: 8,
userSelect: 'none',
touchAction: 'pinch-zoom',
boxShadow: '0 8px 40px rgba(0,0,0,0.6)',
}}
onClick={(e) => e.stopPropagation()}
/>
</div>
</div>,
document.body
);
}

View File

@@ -328,7 +328,7 @@
.msg-bubble.emoji-only::after { display: none; }
.msg-text.emoji-msg {
font-size: 48px;
font-size: 3em;
line-height: 1.1;
margin: 0;
user-select: text;

View File

@@ -166,10 +166,33 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on
await onSend({ content: trimmed || null, imageFile, linkPreview: lp, emojiOnly });
};
// Send a single emoji directly (from picker)
const handleEmojiSend = async (emoji) => {
// Insert emoji at cursor position in the textarea
const handleEmojiSelect = (emoji) => {
setShowEmojiPicker(false);
await onSend({ content: emoji.native, imageFile: null, linkPreview: null, emojiOnly: true });
const el = inputRef.current;
const native = emoji.native;
if (el) {
const start = el.selectionStart ?? 0;
const end = el.selectionEnd ?? 0;
const newText = text.slice(0, start) + native + text.slice(end);
setText(newText);
// Restore focus and move cursor after the inserted emoji
requestAnimationFrame(() => {
el.focus();
const pos = start + native.length;
el.setSelectionRange(pos, pos);
// Resize textarea
el.style.height = 'auto';
const lineHeight = parseFloat(getComputedStyle(el).lineHeight);
const maxHeight = lineHeight * 5 + 20;
el.style.height = Math.min(el.scrollHeight, maxHeight) + 'px';
el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
});
} else {
// No ref yet — just append
setText(prev => prev + native);
}
};
const compressImage = (file) => new Promise((resolve) => {
@@ -326,7 +349,7 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on
<div className="emoji-input-picker" ref={emojiPickerRef}>
<Picker
data={data}
onEmojiSelect={handleEmojiSend}
onEmojiSelect={handleEmojiSelect}
theme="light"
previewPosition="none"
skinTonePosition="none"