add and remove thread members
							parent
							
								
									043a68874b
								
							
						
					
					
						commit
						c87c193118
					
				|  | @ -69,7 +69,8 @@ window.getUser = async (id, name) => { | |||
| 	); | ||||
| }; | ||||
| 
 | ||||
| window.makeUser = (user, url) => { | ||||
| window.makeUser = (user, url, removable) => { | ||||
| 	removable &&= window.currentThread.permissions.admin && !user.permissions?.admin; | ||||
| 	let span = html.node` | ||||
| 		<span class='user' onclick=${function(e) { | ||||
| 			let member = document.getElementById('member'); | ||||
|  | @ -80,6 +81,10 @@ window.makeUser = (user, url) => { | |||
| 			member.user = user; | ||||
| 			document.getElementById('membername').textContent = user.displayname; | ||||
| 			document.getElementById('memberusername').textContent = `${user.name}@${url}`; | ||||
| 			if (removable) | ||||
| 				document.getElementById('removemember').classList.remove('hidden'); | ||||
| 			else | ||||
| 				document.getElementById('removemember').classList.add('hidden'); | ||||
| 		}}>${user.displayname}</span>`; | ||||
| 	span.user = user; | ||||
| 	return span; | ||||
|  | @ -224,12 +229,14 @@ document.body.append(html.node` | |||
| 			<div id='userlist'> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<button id='disconnect' onclick=${function(event) { | ||||
| 			document.getElementById('instance' + this.parentElement.instance.id).remove(); | ||||
| 			window.instancelist.splice(window.instancelist.indexOf(this.parentElement.instance), 1); | ||||
| 			saveInstances(); | ||||
| 			document.getElementById('instance').classList.add('hidden'); | ||||
| 		}}>disconnect</button> | ||||
| 		<p> | ||||
| 			<button id='disconnect' onclick=${function(event) { | ||||
| 				document.getElementById('instance' + this.parentElement.instance.id).remove(); | ||||
| 				window.instancelist.splice(window.instancelist.indexOf(this.parentElement.instance), 1); | ||||
| 				saveInstances(); | ||||
| 				document.getElementById('instance').classList.add('hidden'); | ||||
| 			}}>disconnect</button> | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<hr class='separator' color='#505050'> | ||||
| 	<!-- create thread column goes here --> | ||||
|  | @ -237,13 +244,32 @@ document.body.append(html.node` | |||
| 	<div id='thread' class='column hidden'></div> | ||||
| 	<hr class='separator' color='#505050'> | ||||
| 	<div id='member' class='column hidden'> | ||||
| 		<div class='content'> | ||||
| 			<p> | ||||
| 				<button onclick=${e => | ||||
| 					document.getElementById('member').classList.add('hidden') | ||||
| 				}>close</button> | ||||
| 			</p> | ||||
| 			<p>user: <strong id='membername'></strong></p> | ||||
| 			<p id='memberusername'></p> | ||||
| 		</div> | ||||
| 		<p> | ||||
| 			<button onclick=${e => | ||||
| 				document.getElementById('member').classList.add('hidden') | ||||
| 			}>close</button> | ||||
| 			<button id='removemember' onclick=${e => { | ||||
| 				let member = document.getElementById('member'); | ||||
| 				window.currentInstance.emit('remove_member', { | ||||
| 					thread: window.currentThread.id, | ||||
| 					id: member.user.id | ||||
| 				}, msg => { | ||||
| 					if (!msg.success) { | ||||
| 						console.log('remove_member failed:', msg.message); | ||||
| 						return; | ||||
| 					} | ||||
| 					Array.from(document.getElementById('memberlist').children).find(p => | ||||
| 						p.children[0].user.id == member.user.id).remove(); | ||||
| 					member.classList.add('hidden'); | ||||
| 				}) | ||||
| 			}}>remove from thread</button> | ||||
| 		</p> | ||||
| 		<p>user: <strong id='membername'></strong></p> | ||||
| 		<p id='memberusername'></p> | ||||
| 	</div> | ||||
| `);
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -153,7 +153,7 @@ | |||
| 					margin: 3px; | ||||
| 				} | ||||
| 			} | ||||
| 			#instances { | ||||
| 			#instances, #membershead { | ||||
| 				display: flex; | ||||
| 				justify-content: space-between; | ||||
| 			} | ||||
|  | @ -180,15 +180,12 @@ | |||
| 			#profile { | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
| 			#instance { | ||||
| 			#instance, #member { | ||||
| 				display: flex; | ||||
| 				flex-direction: column; | ||||
| 				justify-content: space-between; | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
| 			#disconnect { | ||||
| 				width: fit-content; | ||||
| 			} | ||||
| 			.thread { | ||||
| 				padding: 2px 3px; | ||||
| 				white-space: pre; | ||||
|  |  | |||
|  | @ -53,27 +53,29 @@ function loadMessages(callback) { | |||
| 			earliestMessage = msg.messages[msg.messages.length - 1].id; | ||||
| 			let users = {}; | ||||
| 			for (let message of msg.messages) { | ||||
| 				let span; | ||||
| 				if (message.name) | ||||
| 					span = window.makeUser({ | ||||
| 						displayname: message.displayname, | ||||
| 						name: message.name, | ||||
| 						id: message.user | ||||
| 					}, instance.url); | ||||
| 				let user; | ||||
| 				if (message.user.name) | ||||
| 					user = message.user; | ||||
| 				else { | ||||
| 					try { | ||||
| 						span = window.makeUser(users[message.userid] || ( | ||||
| 							users[message.userid] = await window.getUser(message.userid) | ||||
| 						), message.userid.split('@')[1]); | ||||
| 					} | ||||
| 					catch (e) { | ||||
| 						console.log(`error getting user ${message.userid}`, e); | ||||
| 						span = html.node`<span>${message.userid}</span>`; | ||||
| 					user = users[message.user.id]; | ||||
| 					if (user === undefined) | ||||
| 						try { | ||||
| 							user = users[message.user.id] = await window.getUser(message.user.id); | ||||
| 						} | ||||
| 						catch (e) { | ||||
| 							console.log(`error getting user ${message.user.id}:`, e); | ||||
| 							users[message.user.id] = false; | ||||
| 						} | ||||
| 					if (user) { | ||||
| 						user.id = message.user.id; | ||||
| 						user.permissions = message.user.permissions; | ||||
| 					} | ||||
| 				} | ||||
| 				message = html.node`<div class='message'>: ${message.content}</div>`; | ||||
| 				message.prepend(span); | ||||
| 				messages.prepend(message); | ||||
| 				let div = html.node`<div class='message'>: ${message.content}</div>`; | ||||
| 				div.prepend(user ? window.makeUser(user, | ||||
| 						user.id.split?.('@')[1] || instance.url, true) | ||||
| 					: html.node`<span>${message.user.id}</span>`); | ||||
| 				messages.prepend(div); | ||||
| 			} | ||||
| 		} | ||||
| 		if (msg.more) | ||||
|  | @ -90,11 +92,8 @@ function loadMessages(callback) { | |||
| 			const messages = document.getElementById('messages'); | ||||
| 			let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight; | ||||
| 			let div = html.node`<div class='message'>: ${message.content}</div>`; | ||||
| 			div.prepend(window.makeUser({ | ||||
| 				name: window.name, | ||||
| 				displayname: window.displayname, | ||||
| 				id: window.id | ||||
| 			}, location.host)); | ||||
| 			div.prepend(window.makeUser(message.user, | ||||
| 				message.user.id.split?.('@')[1] || instance.url, true)); | ||||
| 			messages.append(div); | ||||
| 			if (scroll) | ||||
| 				messages.scroll(0, messages.scrollHeight - messages.clientHeight); | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ function setVisibility() { | |||
| } | ||||
| 
 | ||||
| function chooseThread() { | ||||
| 	if (!document.getElementById('removemember').classList.contains('hidden')) | ||||
| 		document.getElementById('member').classList.add('hidden'); | ||||
| 	const edit = document.getElementById('edit'); | ||||
| 	let thread = this.thread; | ||||
| 	let url = new URL(location); | ||||
|  | @ -46,7 +48,7 @@ function chooseThread() { | |||
| 	window.currentThread = thread; | ||||
| 	window.currentInstance = thread.instance; | ||||
| 	let tab = url.searchParams.get('tab'); | ||||
| 	if (['message', 'space', 'stream'].indexOf(tab) === -1) | ||||
| 	if (!['message', 'space', 'stream'].includes(tab)) | ||||
| 		tab = null; | ||||
| 	if (tab || this.tab) { | ||||
| 		if (tab) | ||||
|  | @ -68,8 +70,19 @@ function chooseThread() { | |||
| 		document.getElementById('memberlist').replaceChildren( | ||||
| 			...await Promise.all(msg.thread.members.map(async member => { | ||||
| 				let p = document.createElement('p'); | ||||
| 				p.append(member.name ? window.makeUser(member, window.currentInstance.url) | ||||
| 					: makeUser(await window.getUser(member.id), member.id.split('@')[1])); | ||||
| 				if (member.name) | ||||
| 					p.append(window.makeUser(member, window.currentInstance.url, true)); | ||||
| 				else | ||||
| 					try { | ||||
| 						let user = await window.getUser(member.id); | ||||
| 						user.id = member.id; | ||||
| 						user.permissions = member.permissions; | ||||
| 						p.append(window.makeUser(user, user.id.split?.('@')[1], true)); | ||||
| 					} | ||||
| 					catch (e) { | ||||
| 						console.log(`error getting user ${member.id}: ${e}`); | ||||
| 						p.append(html.node`<span>${member.id}</span>`); | ||||
| 					} | ||||
| 				return p; | ||||
| 			})) | ||||
| 		); | ||||
|  | @ -137,7 +150,7 @@ function newThread() { | |||
| 			return; | ||||
| 		let at = name.value.split('@'); | ||||
| 		let url = at[1] || instancediv.instance.url; | ||||
| 		let error = document.getElementById('membererror'); | ||||
| 		let error = document.getElementById('newmembererror'); | ||||
| 		let user = await window.getUser('@' + url, at[0]); | ||||
| 		if (!user) { | ||||
| 			error.innerText = 'user not found'; | ||||
|  | @ -213,8 +226,8 @@ function newThread() { | |||
| 					addMember(); | ||||
| 				} | ||||
| 			}} /> | ||||
| 			<button id='addmember' type='button' onclick=${addMember}>add</button> | ||||
| 			<p id='membererror'></p> | ||||
| 			<button type='button' onclick=${addMember}>add</button> | ||||
| 			<p id='newmembererror'></p> | ||||
| 			<div id='newmembers'> | ||||
| 				<p class='member'>${members[0].name}</p> | ||||
| 			</div> | ||||
|  | @ -306,6 +319,51 @@ function clickedTab(event) { | |||
| 	window.history.pushState(null, '', url.toString()); | ||||
| } | ||||
| 
 | ||||
| async function addMember() { | ||||
| 	let name = document.getElementById('addmembername'); | ||||
| 	let error = document.getElementById('membererror'); | ||||
| 	function close() { | ||||
| 		error.innerText = ''; | ||||
| 		name.value = ''; | ||||
| 		document.getElementById('addmember').classList.add('hidden'); | ||||
| 	} | ||||
| 	if (!name.value) { | ||||
| 		close(); | ||||
| 		return; | ||||
| 	} | ||||
| 	let at = name.value.split('@'); | ||||
| 	let url = at[1] || window.currentInstance.url; | ||||
| 	let user = await window.getUser('@' + url, at[0]); | ||||
| 	if (!user) { | ||||
| 		error.innerText = 'user not found'; | ||||
| 		return; | ||||
| 	} | ||||
| 	error.innerText = ''; | ||||
| 	if (window.currentInstance.url !== url) { | ||||
| 		user.id += '@' + url; | ||||
| 		user.name += '@' + url; | ||||
| 	} | ||||
| 	for (let p of document.getElementById('memberlist').children) | ||||
| 		if (p.children[0].user.id == user.id) { | ||||
| 			close(); | ||||
| 			return; | ||||
| 		} | ||||
| 	window.currentInstance.emit('add_member', { | ||||
| 		id: String(user.id), | ||||
| 		thread: window.currentThread.id | ||||
| 	}, msg => { | ||||
| 		if (!msg.success) { | ||||
| 			console.log('add_member failed:', msg.message); | ||||
| 			error.textContent = 'error: ' + msg.message; | ||||
| 			return; | ||||
| 		} | ||||
| 		close(); | ||||
| 		let p = document.createElement('p'); | ||||
| 		p.append(window.makeUser(user, url, true)); | ||||
| 		document.getElementById('memberlist').append(p); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function loadThreads(instancediv, select) { | ||||
| 	function makeThread(thread) { | ||||
| 		thread.instance = instancediv.instance; | ||||
|  | @ -344,7 +402,20 @@ async function loadThreads(instancediv, select) { | |||
| 			<hr class='separator' color='#505050'> | ||||
| 			<div id='members' class='column hidden'> | ||||
| 				<p id='visibility'></p> | ||||
| 				<h4>members</h4> | ||||
| 				<div id='membershead'> | ||||
| 					<strong>members</strong> | ||||
| 					<button onclick=${e => { | ||||
| 						document.getElementById('addmember').classList.remove('hidden'); | ||||
| 						document.getElementById('addmembername').focus(); | ||||
| 					}}>add</button> | ||||
| 				</div> | ||||
| 				<div id='addmember' class='hidden'> | ||||
| 					<input id='addmembername' onblur=${addMember} onkeydown=${event => { | ||||
| 						if (event.key === 'Enter') | ||||
| 							addMember(); | ||||
| 					}}> | ||||
| 					<p id='membererror'></p> | ||||
| 				</div> | ||||
| 				<div id='memberlist'> | ||||
| 				</div> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -25,17 +25,26 @@ async function send_message(msg, respond) { | |||
| 		await db.query('select user from member where member.thread = ?', msg.thread) | ||||
| 	).rows.map(i => i.user); | ||||
| 	// get perms
 | ||||
| 	const permissions = await db.query( | ||||
| 		`select * from permission
 | ||||
| 		where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'`,
 | ||||
| 		msg.thread); | ||||
| 	const perms = (await db.query( | ||||
| 		`select * from permission where thread = ?`, | ||||
| 		msg.thread)).rows; | ||||
| 	let userperms = {}; | ||||
| 	for (let p of perms) | ||||
| 		if (p.type === 'user' && p.user == msg.auth_user.id) | ||||
| 			userperms[p.permission] = ['admin', 'post', 'view'].includes(p.permission) | ||||
| 				? p.value === 'true' : p.value; | ||||
| 	for (let id in vybe.users) { | ||||
| 		if (permissions.rows.length > 0 || members.includes(id)) { | ||||
| 		if (perms.find(p => p.type === 'everyone' && p.permission === 'view' && p.value === 'true') | ||||
| 			|| members.includes(id)) { | ||||
| 			for (let s of vybe.users[id].sockets) { | ||||
| 				s.emit('new_message', { | ||||
| 					id, | ||||
| 					userid: msg.auth_user.id, | ||||
| 					name: msg.auth_user.displayname, | ||||
| 					user: { | ||||
| 						id: msg.auth_user.id, | ||||
| 						name: msg.auth_user.name, | ||||
| 						displayname: msg.auth_user.displayname, | ||||
| 						permissions: userperms | ||||
| 					}, | ||||
| 					content: msg.message, | ||||
| 					thread: msg.thread | ||||
| 				}); | ||||
|  | @ -67,8 +76,8 @@ async function get_history(msg, respond) { | |||
| 			message: "you can't view this thread" | ||||
| 		}); | ||||
| 	} | ||||
| 	const messages = (await db.query( | ||||
| 		`select coalesce(displayname, name) as displayname, name, user as userid, post.id, content
 | ||||
| 	const messages = (await db.query(` | ||||
| 		select coalesce(displayname, name) as displayname, name, user as userid, post.id, content | ||||
| 		from post | ||||
| 		left join user on post.user = user.id | ||||
| 		where ${msg.before ? 'post.id < ? and' : ''} | ||||
|  | @ -77,9 +86,25 @@ async function get_history(msg, respond) { | |||
| 		limit 101`,
 | ||||
| 		msg.before ? [msg.before, msg.thread] : [msg.thread] | ||||
| 	)).rows; | ||||
| 	let perms = {}; | ||||
| 	for (let p of (await db.query( | ||||
| 		`select * from permission where type = 'user' and thread = ?`, | ||||
| 		msg.thread)).rows) | ||||
| 		(perms[p.user] || (perms[p.user] = {}))[p.permission] = | ||||
| 			['admin', 'post', 'view'].includes(p.permission) | ||||
| 				? p.value === 'true' : p.value; | ||||
| 	return respond({ | ||||
| 		success: true, | ||||
| 		messages: messages.slice(0, 100), | ||||
| 		messages: messages.slice(0, 100).map(message => ({ | ||||
| 			id: message.id, | ||||
| 			user: { | ||||
| 				id: message.userid, | ||||
| 				name: message.name, | ||||
| 				displayname: message.displayname, | ||||
| 				permissions: perms[message.userid] | ||||
| 			}, | ||||
| 			content: message.content | ||||
| 		})), | ||||
| 		more: messages.length > 100 | ||||
| 	}); | ||||
| } | ||||
|  |  | |||
|  | @ -373,9 +373,83 @@ async function edit_thread(msg, respond) { | |||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function add_member(msg, respond) { | ||||
| 	if (!msg.thread || !msg.id) { | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: 'invalid msg' | ||||
| 		}); | ||||
| 	} | ||||
| 	if (!(await check_permission(msg.auth_user.id, msg.thread)).admin) | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: "user doesn't have permission" | ||||
| 		}); | ||||
| 	if ((await db.query( | ||||
| 			`select user from thread
 | ||||
| 			join member on thread.id = member.thread | ||||
| 			where thread.id = ? and user = ?`,
 | ||||
| 			[msg.thread, msg.id] | ||||
| 		)).rows.length) | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: 'user is already a member of this thread' | ||||
| 		}); | ||||
| 	await db.query(` | ||||
| 		insert into member (thread, user) values (?, ?)`,
 | ||||
| 		[msg.thread, msg.id]); | ||||
| 	return respond({ | ||||
| 		success: true | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| async function remove_member(msg, respond) { | ||||
| 	if (!msg.thread || !msg.id) { | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: 'invalid msg' | ||||
| 		}); | ||||
| 	} | ||||
| 	if (!(await check_permission(msg.auth_user.id, msg.thread)).admin) | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: "user doesn't have permission" | ||||
| 		}); | ||||
| 	let members = (await db.query( | ||||
| 		`select member.user, p.value from thread
 | ||||
| 		join member on thread.id = member.thread | ||||
| 		left join permission p on p.user = member.user | ||||
| 			and p.type = 'user' and p.permission = 'admin' and p.value = 'true' | ||||
| 		where thread.id = ?`,
 | ||||
| 		msg.thread | ||||
| 	)).rows; | ||||
| 	if (!members.find(member => member.user == msg.id)) | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: "user isn't a member of this thread" | ||||
| 		}); | ||||
| 	members = members.filter(member => member.value); | ||||
| 	if (members.length <= 1 && members[0]?.user == msg.id) | ||||
| 		return respond({ | ||||
| 			success: false, | ||||
| 			message: "can't remove the only admin of the thread" | ||||
| 		}); | ||||
| 	await db.query(` | ||||
| 		delete from member where thread = ? and user = ?`,
 | ||||
| 		[msg.thread, msg.id]); | ||||
| 	await db.query(` | ||||
| 		delete from permission where type = 'user' and thread = ? and user = ?`,
 | ||||
| 		[msg.thread, msg.id]); | ||||
| 	return respond({ | ||||
| 		success: true | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| 	create_thread: authwrap(create_thread), | ||||
| 	list_threads: authwrap(list_threads), | ||||
| 	get_thread: authwrap(get_thread), | ||||
| 	edit_thread: authwrap(edit_thread) | ||||
| 	edit_thread: authwrap(edit_thread), | ||||
| 	add_member: authwrap(add_member), | ||||
| 	remove_member: authwrap(remove_member) | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue