Compare commits
	
		
			2 Commits
		
	
	
		
			5c4d258384
			...
			f6df3901b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f6df3901b1 | |||
| 92d1faf3e5 | 
| @ -1,42 +1,45 @@ | ||||
| { | ||||
|   "name": "chat", | ||||
|   "version": "0.1.0", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "react": "^18.2.0", | ||||
|     "react-dom": "^18.2.0", | ||||
|     "react-scripts": "^5.0.1", | ||||
|     "react-string-replace": "^1.1.0", | ||||
|     "socket.io-client": "^4.5.2", | ||||
|     "uuid": "^9.0.0" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "react-scripts start", | ||||
|     "build": "react-scripts build", | ||||
|     "test": "react-scripts test", | ||||
|     "eject": "react-scripts eject" | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "extends": [ | ||||
|       "react-app", | ||||
|       "react-app/jest" | ||||
|     ] | ||||
|   }, | ||||
|   "browserslist": { | ||||
|     "production": [ | ||||
|       ">0.2%", | ||||
|       "not dead", | ||||
|       "not op_mini all" | ||||
|     ], | ||||
|     "development": [ | ||||
|       "last 1 chrome version", | ||||
|       "last 1 firefox version", | ||||
|       "last 1 safari version" | ||||
|     ] | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "autoprefixer": "^10.4.8", | ||||
|     "postcss": "^8.4.16", | ||||
|     "tailwindcss": "^3.1.8" | ||||
|   } | ||||
| 	"name": "chat", | ||||
| 	"version": "0.1.0", | ||||
| 	"private": true, | ||||
| 	"dependencies": { | ||||
| 		"react": "^18.2.0", | ||||
| 		"react-dom": "^18.2.0", | ||||
| 		"react-scripts": "^5.0.1", | ||||
| 		"react-string-replace": "^1.1.0", | ||||
| 		"socket.io-client": "^4.5.2", | ||||
| 		"uuid": "^9.0.0" | ||||
| 	}, | ||||
| 	"scripts": { | ||||
| 		"start": "react-scripts start", | ||||
| 		"build": "react-scripts build", | ||||
| 		"test": "react-scripts test", | ||||
| 		"eject": "react-scripts eject" | ||||
| 	}, | ||||
| 	"eslintConfig": { | ||||
| 		"extends": [ | ||||
| 			"react-app", | ||||
| 			"react-app/jest" | ||||
| 		], | ||||
| 		"rules": { | ||||
| 			"react/jsx-no-target-blank": "off" | ||||
| 		} | ||||
| 	}, | ||||
| 	"browserslist": { | ||||
| 		"production": [ | ||||
| 			">0.2%", | ||||
| 			"not dead", | ||||
| 			"not op_mini all" | ||||
| 		], | ||||
| 		"development": [ | ||||
| 			"last 1 chrome version", | ||||
| 			"last 1 firefox version", | ||||
| 			"last 1 safari version" | ||||
| 		] | ||||
| 	}, | ||||
| 	"devDependencies": { | ||||
| 		"autoprefixer": "^10.4.8", | ||||
| 		"postcss": "^8.4.16", | ||||
| 		"tailwindcss": "^3.1.8" | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										115
									
								
								app/src/App.js
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								app/src/App.js
									
									
									
									
									
								
							| @ -76,7 +76,7 @@ function Chat({user, setUser}) { | ||||
| 
 | ||||
| 	return ( | ||||
| 		<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} /> | ||||
| 			<ChatInput sendMessage={sendMessage} user={user} updateUser={updateUser} setSettingsModalOpen={setSettingsModalOpen} socket={socket} /> | ||||
| 			<SettingsModal open={settingsModalOpen} setOpen={setSettingsModalOpen} user={user} updateUser={updateUser} /> | ||||
| @ -90,78 +90,64 @@ function Chat({user, setUser}) { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| function MessageList({messages, user, updateUser}) { | ||||
| 
 | ||||
| 	var [scrollLock, setScrollLock] = useState(true); | ||||
| function MessageList({messages, setMessages}) { | ||||
| 	var [atBottom, setAtBottom] = useState(true); | ||||
| 	var [atTop, setAtTop] = useState(false); | ||||
| 	var endRef = useRef(); | ||||
| 
 | ||||
| 	function onScroll(event) { | ||||
| 		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 => { | ||||
| 		if (scrollLock) scrollToBottom(); | ||||
| 	var scrollToBottom = () => endRef.current?.scrollIntoView(); | ||||
| 	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)}> | ||||
| 		{messages.map(message => { | ||||
| 
 | ||||
| 			function onAuthorClick(event) { | ||||
| 				if (message.user.uuid !== user.uuid) return; | ||||
| 				event.preventDefault(); | ||||
| 				var website = prompt("Set website URL", user.website); | ||||
| 				if (website) updateUser({website}); | ||||
| 			} | ||||
| 
 | ||||
| 			if (message.user) | ||||
| 			// eslint-disable-next-line react/jsx-no-target-blank
 | ||||
| 			var prefix = <b>{message.user.website ? <a | ||||
| 				href={message.user.website} | ||||
| 				target="_blank" | ||||
| 				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) { | ||||
| 				let url = BASE_URL + `/file/${message._id}/${message.file.name}`; | ||||
| 				let embed = | ||||
| 					message.file.type?.startsWith("image") ? | ||||
| 						<img src={url} alt={message.file.name} className="max-h-32 inline-block align-top border" /> | ||||
| 					: message.file.type?.startsWith("video") ? | ||||
| 						<video className='max-h-64 inline-block align-top border' controls> | ||||
| 							<source src={url} type={message.file.type} /> | ||||
| 						</video> | ||||
| 					: message.file.type?.startsWith("audio") ? | ||||
| 						<audio className='max-h-64 inline-block align-top' controls> | ||||
| 							<source src={url} type={message.file.type} /> | ||||
| 						</audio> | ||||
| 					: <span>{message.file.name}</span> | ||||
| 				// 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}> | ||||
| 				{prefix} {content} {file} | ||||
| 			</li> | ||||
| 
 | ||||
| 		})} | ||||
| 		{messages.map(message => <Message message={message} key={message._id} />)} | ||||
| 		<div ref={endRef}></div> | ||||
| 	</ul> | ||||
| } | ||||
| 
 | ||||
| function Message({message}) { | ||||
| 	if (message.user) { | ||||
| 		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> | ||||
| 	} | ||||
| 	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) { | ||||
| 		let url = BASE_URL + `/file/${message._id}/${message.file.name}`; | ||||
| 		var file = | ||||
| 			message.file.type?.startsWith("image") ? | ||||
| 				<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") ? | ||||
| 				<video className='max-h-64 inline-block align-top border' controls> | ||||
| 					<source src={url} type={message.file.type} /> | ||||
| 				</video> | ||||
| 			: message.file.type?.startsWith("audio") ? | ||||
| 				<audio className='max-h-64 inline-block align-top' controls> | ||||
| 					<source src={url} type={message.file.type} /> | ||||
| 				</audio> | ||||
| 			: <a href={url} target="_blank" rel="noopener" style={{color:"revert",textDecoration:"revert"}}>{message.file.name}</a> | ||||
| 	} | ||||
| 
 | ||||
| 	return <li>{prefix} {content} {file}</li> | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -244,8 +230,10 @@ function EmojiPicker({open, setOpen, setChatInputContent}) { | ||||
| 		setChatInputContent(content => `${content} :${event.target.dataset.emoji}:`); | ||||
| 	}; | ||||
| 	if (open) | ||||
| 	return <div className='w-64 h-64 rounded border fixed right-6 bottom-16 overflow-auto bg-white dark:bg-black'> | ||||
| 		{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} />)} | ||||
| 	return <div className='fixed top-0 left-0 w-full h-full' onClick={e=>setOpen(false)}> | ||||
| 		<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> | ||||
| } | ||||
| 
 | ||||
| @ -343,7 +331,6 @@ function processMessageContent(content) { | ||||
| 	if (!content) return; | ||||
| 	// hyperlinks
 | ||||
| 	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> | ||||
| 	) | ||||
| 	// emoji
 | ||||
|  | ||||
| @ -24,6 +24,12 @@ app.use((req, res, next) => { | ||||
| 	res.header("Access-Control-Allow-Methods", "*"); | ||||
| 	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) => { | ||||
| 	try { | ||||
| 		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) | ||||
| 				} : 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(); | ||||
| 			socket.emit("messages", history); | ||||
| 			socket.on("type", () => { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user