Compare commits

...

2 Commits

Author SHA1 Message Date
f6df3901b1 crappy message history load 2022-09-18 16:52:51 -05:00
92d1faf3e5 emoji selector improvements 2022-09-15 00:03:10 -05:00
3 changed files with 101 additions and 105 deletions

View File

@ -20,7 +20,10 @@
"extends": [ "extends": [
"react-app", "react-app",
"react-app/jest" "react-app/jest"
] ],
"rules": {
"react/jsx-no-target-blank": "off"
}
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

View File

@ -76,7 +76,7 @@ function Chat({user, setUser}) {
return ( return (
<div className="Chat h-full flex flex-col"> <div className="Chat h-full flex flex-col">
<MessageList messages={messages} user={user} updateUser={updateUser} /> <MessageList messages={messages} setMessages={setMessages} />
<UserList users={users} updateUser={updateUser} user={user} socket={socket} /> <UserList users={users} updateUser={updateUser} user={user} socket={socket} />
<ChatInput sendMessage={sendMessage} user={user} updateUser={updateUser} setSettingsModalOpen={setSettingsModalOpen} socket={socket} /> <ChatInput sendMessage={sendMessage} user={user} updateUser={updateUser} setSettingsModalOpen={setSettingsModalOpen} socket={socket} />
<SettingsModal open={settingsModalOpen} setOpen={setSettingsModalOpen} user={user} updateUser={updateUser} /> <SettingsModal open={settingsModalOpen} setOpen={setSettingsModalOpen} user={user} updateUser={updateUser} />
@ -90,56 +90,50 @@ function Chat({user, setUser}) {
function MessageList({messages, user, updateUser}) { function MessageList({messages, setMessages}) {
var [atBottom, setAtBottom] = useState(true);
var [scrollLock, setScrollLock] = useState(true); var [atTop, setAtTop] = useState(false);
var endRef = useRef(); var endRef = useRef();
function onScroll(event) { function onScroll(event) {
var { scrollTop, scrollHeight, clientHeight } = event.target; var { scrollTop, scrollHeight, clientHeight } = event.target;
setScrollLock(scrollHeight - (scrollTop + clientHeight) < 32); setAtBottom(scrollHeight - (scrollTop + clientHeight) < 32);
setAtTop(scrollTop < 32);
}; };
function scrollToBottom() {
endRef.current?.scrollIntoView();
}
useEffect(() => {
if (scrollLock) scrollToBottom();
});
var observer = new ResizeObserver(entries => { var scrollToBottom = () => endRef.current?.scrollIntoView();
if (scrollLock) scrollToBottom(); useEffect(() => {
if (atBottom) scrollToBottom();
}); });
var observer = new ResizeObserver(() => atBottom && scrollToBottom());
useEffect(()=>{
if (atTop) loadOlderMessages();
}, [atTop]);
async function loadOlderMessages() {
var olderMessages = await fetch(BASE_URL+"/messages?before="+messages[0].timestamp).then(res => res.json());
console.debug("loadOlderMessages", olderMessages);
setMessages(messages => [...olderMessages, ...messages]);
}
return <ul className="p-4 w-full flex-1 overflow-y-auto break-words" onScroll={onScroll} onLoad={e => observer.observe(e.target)}> return <ul className="p-4 w-full flex-1 overflow-y-auto break-words" onScroll={onScroll} onLoad={e => observer.observe(e.target)}>
{messages.map(message => { {messages.map(message => <Message message={message} key={message._id} />)}
<div ref={endRef}></div>
function onAuthorClick(event) { </ul>
if (message.user.uuid !== user.uuid) return;
event.preventDefault();
var website = prompt("Set website URL", user.website);
if (website) updateUser({website});
} }
if (message.user) function Message({message}) {
// eslint-disable-next-line react/jsx-no-target-blank if (message.user) {
var prefix = <b>{message.user.website ? <a var prefix = <b>{message.user.website ? <a href={message.user.website} target="_blank" rel="noopener" className={message.user.website && "hover:underline"}>{message.user.name}</a> : message.user.name}: </b>
href={message.user.website} }
target="_blank" var content = <span className={(message.user ? '' : " font-bold")} style={{color: message.color || message.user?.color}} title={new Date(message.timestamp).toLocaleString()}>{processMessageContent(message.content)}</span>
rel="noopener"
//onClick={onAuthorClick}
className={message.user.website && "hover:underline"}
>{message.user.name}</a> : message.user.name}: </b>
var content = <span
className={(message.user ? '' : " font-bold")}
style={{color: message.color || message.user?.color}}
title={new Date(message.timestamp).toLocaleString()}
>{processMessageContent(message.content)}</span>
if (message.file) { if (message.file) {
let url = BASE_URL + `/file/${message._id}/${message.file.name}`; let url = BASE_URL + `/file/${message._id}/${message.file.name}`;
let embed = var file =
message.file.type?.startsWith("image") ? message.file.type?.startsWith("image") ?
<img src={url} alt={message.file.name} className="max-h-32 inline-block align-top border" /> <a href={url} target="_blank" rel="noopener" style={{color:"revert",textDecoration:"revert"}}><img src={url} alt={message.file.name} className="max-h-32 inline-block align-top border" /></a>
: message.file.type?.startsWith("video") ? : message.file.type?.startsWith("video") ?
<video className='max-h-64 inline-block align-top border' controls> <video className='max-h-64 inline-block align-top border' controls>
<source src={url} type={message.file.type} /> <source src={url} type={message.file.type} />
@ -148,18 +142,10 @@ function MessageList({messages, user, updateUser}) {
<audio className='max-h-64 inline-block align-top' controls> <audio className='max-h-64 inline-block align-top' controls>
<source src={url} type={message.file.type} /> <source src={url} type={message.file.type} />
</audio> </audio>
: <span>{message.file.name}</span> : <a href={url} target="_blank" rel="noopener" style={{color:"revert",textDecoration:"revert"}}>{message.file.name}</a>
// eslint-disable-next-line react/jsx-no-target-blank
var file = <a href={url} target="_blank" rel="noopener" style={{color:"revert",textDecoration:"revert"}}>{embed}</a>;
} }
return <li key={message._id}> return <li>{prefix} {content} {file}</li>
{prefix} {content} {file}
</li>
})}
<div ref={endRef}></div>
</ul>
} }
@ -244,8 +230,10 @@ function EmojiPicker({open, setOpen, setChatInputContent}) {
setChatInputContent(content => `${content} :${event.target.dataset.emoji}:`); setChatInputContent(content => `${content} :${event.target.dataset.emoji}:`);
}; };
if (open) if (open)
return <div className='w-64 h-64 rounded border fixed right-6 bottom-16 overflow-auto bg-white dark:bg-black'> return <div className='fixed top-0 left-0 w-full h-full' onClick={e=>setOpen(false)}>
{emojis.map(emoji => <img src={BASE_URL+"/emoji/"+emoji} title={`:${emoji}:`} alt={`:${emoji}:`} key={emoji} data-emoji={emoji} className="w-8 h-8 inline-block m-1 cursor-pointer hover:border" onClick={onEmojiClick} />)} <div className='w-[258px] h-64 rounded border fixed right-6 bottom-16 overflow-auto bg-white dark:bg-black p-2' onClick={e=>e.stopPropagation()}>
{emojis.map(emoji => <img src={BASE_URL+"/emoji/"+emoji} title={`:${emoji}:`} alt={`:${emoji}:`} key={emoji} data-emoji={emoji} className="w-10 h-10 inline-block p-0.5 cursor-pointer border border-transparent hover:border-gray-600" onClick={onEmojiClick} />)}
</div>
</div> </div>
} }
@ -343,7 +331,6 @@ function processMessageContent(content) {
if (!content) return; if (!content) return;
// hyperlinks // hyperlinks
content = reactStringReplace(content, /(https?:\/\/\S+)/gi, link => content = reactStringReplace(content, /(https?:\/\/\S+)/gi, link =>
// eslint-disable-next-line react/jsx-no-target-blank
<a href={link} target="_blank" rel="noopener" style={{color: "revert", textDecoration: "revert"}}>{link}</a> <a href={link} target="_blank" rel="noopener" style={{color: "revert", textDecoration: "revert"}}>{link}</a>
) )
// emoji // emoji

View File

@ -24,6 +24,12 @@ app.use((req, res, next) => {
res.header("Access-Control-Allow-Methods", "*"); res.header("Access-Control-Allow-Methods", "*");
next(); next();
}); });
app.get("/messages", (req, res, next) => {
var query = req.query.before ? {timestamp: {$lt: new Date(req.query.before)}} : {};
messages.find(query).project({"file.data": 0, "user.ip": 0}).sort({timestamp: -1}).limit(100).toArray().then(messages => {
res.send(messages.reverse());
}).catch(e => next(e));
});
app.get("/file/:message_id/:filename", async (req, res, next) => { app.get("/file/:message_id/:filename", async (req, res, next) => {
try { try {
var doc = await messages.findOne({_id: new ObjectId(req.params.message_id)}, {file:1}); var doc = await messages.findOne({_id: new ObjectId(req.params.message_id)}, {file:1});
@ -107,7 +113,7 @@ dbclient.connect().then(async () => {
type: m.file.type?.substring(0,64) type: m.file.type?.substring(0,64)
} : undefined } : undefined
})); }));
var history = await messages.find().project({"file.data": 0}).sort({timestamp: -1}).limit(100).toArray(); var history = await messages.find().project({"file.data": 0, "user.ip": 0}).sort({timestamp: -1}).limit(100).toArray();
history = history.reverse(); history = history.reverse();
socket.emit("messages", history); socket.emit("messages", history);
socket.on("type", () => { socket.on("type", () => {