view users
							parent
							
								
									b8b7a7edd6
								
							
						
					
					
						commit
						62816d255f
					
				|  | @ -1,10 +1,11 @@ | |||
| import { render, html } from '/uhtml.js'; | ||||
| import loadThreads from '/thread.js'; | ||||
| 
 | ||||
| function changeName(e) { | ||||
| function saveProfile(e) { | ||||
| 	let displayname = document.getElementById('newname').value; | ||||
| 	window.emit('update_user', { | ||||
| 		displayname | ||||
| 		displayname, | ||||
| 		public: document.getElementById('profilepublic').checked | ||||
| 	}, msg => { | ||||
| 		if (!msg.success) { | ||||
| 			console.log('update_user error: ', msg.message); | ||||
|  | @ -13,7 +14,7 @@ function changeName(e) { | |||
| 		} | ||||
| 		window.displayname = displayname; | ||||
| 		document.getElementById('savename').classList.add('hidden'); | ||||
| 	}) | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| window.connectInstance = async url => { | ||||
|  | @ -59,12 +60,31 @@ window.getUser = async (id, name) => { | |||
| 	return new Promise((resolve, reject) => | ||||
| 		instance.socket.emit('get_user', { id, name }, msg => { | ||||
| 			if (!msg.success) | ||||
| 				return reject('get_user failed: ' + msg.message); | ||||
| 				if (msg.message === 'user not found') | ||||
| 					return resolve(); | ||||
| 				else | ||||
| 					return reject(msg.message); | ||||
| 			resolve(msg.user); | ||||
| 		}) | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| window.makeUser = (user, url) => { | ||||
| 	let span = html.node` | ||||
| 		<span class='user' onclick=${function(e) { | ||||
| 			let member = document.getElementById('member'); | ||||
| 			if (member.user === user) | ||||
| 				member.classList.toggle('hidden'); | ||||
| 			else | ||||
| 				member.classList.remove('hidden'); | ||||
| 			member.user = user; | ||||
| 			document.getElementById('membername').textContent = user.displayname; | ||||
| 			document.getElementById('memberusername').textContent = `${user.name}@${url}`; | ||||
| 		}}>${user.displayname}</span>`; | ||||
| 	span.user = user; | ||||
| 	return span; | ||||
| }; | ||||
| 
 | ||||
| async function authenticateInstance(div, select) { | ||||
| 	return new Promise((resolve, reject) => | ||||
| 		div.instance.socket.emit('authenticate', { | ||||
|  | @ -107,8 +127,11 @@ function instanceClicked(event) { | |||
| 		let userlist = document.getElementById('userlist'); | ||||
| 		userlist.innerHTML = ''; | ||||
| 		instance.emit('list_users', {}, msg => | ||||
| 			userlist.innerHTML = msg.users.map(user => | ||||
| 				`<p>${user.displayname}</p>`).join('\n') | ||||
| 			userlist.replaceChildren(...msg.users.map(user => { | ||||
| 				let p = document.createElement('p'); | ||||
| 				p.append(window.makeUser(user, instance.url)); | ||||
| 				return p; | ||||
| 			})) | ||||
| 		); | ||||
| 		div.instance = instance; | ||||
| 	} | ||||
|  | @ -155,11 +178,15 @@ document.body.append(html.node` | |||
| 			if (window.displayname === this.value) | ||||
| 				document.getElementById('savename').classList.add('hidden'); | ||||
| 			else if (event.key === 'Enter') | ||||
| 				changeName(); | ||||
| 				saveProfile(); | ||||
| 			else | ||||
| 				document.getElementById('savename').classList.remove('hidden'); | ||||
| 		}}> | ||||
| 		<button class='hidden' id='savename' onclick=${changeName}>save</button> | ||||
| 		<button class='hidden' id='savename' onclick=${saveProfile}>save</button> | ||||
| 		<p> | ||||
| 			<input id='profilepublic' type='checkbox' oninput=${saveProfile}> | ||||
| 			<label for='profilepublic'>show profile in instance users</label> | ||||
| 		</p> | ||||
| 		<label class='heading'>authentication requests</label> | ||||
| 		<div id='authrequests'></div> | ||||
| 	</div> | ||||
|  | @ -207,6 +234,17 @@ document.body.append(html.node` | |||
| 	<hr class='separator' color='#505050'> | ||||
| 	<!-- create thread column goes here --> | ||||
| 	<hr class='separator' color='#505050'> | ||||
| 	<div id='thread' class='column hidden'></div> | ||||
| 	<hr class='separator' color='#505050'> | ||||
| 	<div id='member' class='column hidden'> | ||||
| 		<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> | ||||
| `);
 | ||||
| 
 | ||||
| for (let i = 0; i < instancelist.length; ++i) { | ||||
|  |  | |||
|  | @ -32,17 +32,16 @@ async function auth() { | |||
| 		pubkey: window.keys.armored.publicKey | ||||
| 	}, | ||||
| 	async msg => { | ||||
| 		let register = document.getElementById('register'); | ||||
| 		if (!msg.success) { | ||||
| 			console.log('authenticate failed:', msg.message); | ||||
| 			if (document.getElementById('app')) | ||||
| 				location.reload(); | ||||
| 			document.getElementById('result').innerText = msg.message; | ||||
| 			register.classList.remove('hidden'); | ||||
| 			return; | ||||
| 		} | ||||
| 		localStorage.setItem('keys', JSON.stringify(window.keys.armored)); | ||||
| 		localStorage.setItem('name', window.name = msg.name); | ||||
| 		localStorage.setItem('id', window.id = msg.id); | ||||
| 		register.classList.add('hidden'); | ||||
| 		window.displayname = msg.displayname; | ||||
| 		if (window.instancelist) { | ||||
| 			if (window.instancelist[0].url !== location.host | ||||
|  | @ -62,8 +61,10 @@ async function auth() { | |||
| 		instancelist[0].socket = window.socket; | ||||
| 		instancelist[0].emit = window.emit; | ||||
| 		window.instances = Object.fromEntries(instancelist.map(i => [i.url, i])); | ||||
| 		document.getElementById('register')?.remove(); | ||||
| 		const { authRequest } = await import('/app.js'); | ||||
| 		msg.authrequests.forEach(authRequest); | ||||
| 		document.getElementById('profilepublic').checked = msg.public; | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ | |||
| 				&::placeholder { | ||||
| 					color: #aaa; | ||||
| 				} | ||||
| 				&[type='radio'] { | ||||
| 				&[type='radio'], &[type='checkbox'] { | ||||
| 					position: relative; | ||||
| 					top: 2px; | ||||
| 				} | ||||
|  | @ -107,7 +107,7 @@ | |||
| 				margin: 8px 2px; | ||||
| 			} | ||||
| 			.separator:has(+ .separator), | ||||
| 			*.hidden + .separator, | ||||
| 			*.hidden:first-child + .separator, | ||||
| 			.separator:has(+ *.hidden), | ||||
| 			.separator:last-child { | ||||
| 				display: none; | ||||
|  | @ -171,6 +171,12 @@ | |||
| 				padding: 6px; | ||||
| 				background-color: #222; | ||||
| 			} | ||||
| 			.user { | ||||
| 				white-space: pre-wrap; | ||||
| 				&:hover { | ||||
| 					background-color: #444; | ||||
| 				} | ||||
| 			} | ||||
| 			#profile { | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
|  | @ -257,6 +263,9 @@ | |||
| 				min-width: 140px; | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
| 			#member { | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
| 			#editthread { | ||||
| 				max-width: fit-content; | ||||
| 			} | ||||
|  |  | |||
|  | @ -53,22 +53,27 @@ function loadMessages(callback) { | |||
| 			earliestMessage = msg.messages[msg.messages.length - 1].id; | ||||
| 			let users = {}; | ||||
| 			for (let message of msg.messages) { | ||||
| 				if (!message.name) { | ||||
| 				let span; | ||||
| 				if (message.name) | ||||
| 					span = window.makeUser({ | ||||
| 						displayname: message.displayname, | ||||
| 						name: message.name, | ||||
| 						id: message.user | ||||
| 					}, instance.url); | ||||
| 				else { | ||||
| 					try { | ||||
| 						message.name = (users[message.userid] || ( | ||||
| 						span = window.makeUser(users[message.userid] || ( | ||||
| 							users[message.userid] = await window.getUser(message.userid) | ||||
| 						)).displayname; | ||||
| 						), message.userid.split('@')[1]); | ||||
| 					} | ||||
| 					catch (e) { | ||||
| 						console.log(`error getting user ${message.userid}`, e); | ||||
| 						message.name = message.userid; | ||||
| 						span = html.node`<span>${message.userid}</span>`; | ||||
| 					} | ||||
| 				} | ||||
| 				messages.prepend(html.node` | ||||
| 					<div class='message'> | ||||
| 						<strong>${message.name}: </strong> | ||||
| 						${message.content} | ||||
| 					</div>`); | ||||
| 				message = html.node`<div class='message'>: ${message.content}</div>`; | ||||
| 				message.prepend(span); | ||||
| 				messages.prepend(message); | ||||
| 			} | ||||
| 		} | ||||
| 		if (msg.more) | ||||
|  | @ -84,11 +89,13 @@ function loadMessages(callback) { | |||
| 				return; | ||||
| 			const messages = document.getElementById('messages'); | ||||
| 			let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight; | ||||
| 			messages.appendChild(html.node` | ||||
| 				<div class='message'> | ||||
| 					<strong>${message.name}: </strong> | ||||
| 					${message.content} | ||||
| 				</div>`); | ||||
| 			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)); | ||||
| 			messages.append(div); | ||||
| 			if (scroll) | ||||
| 				messages.scroll(0, messages.scrollHeight - messages.clientHeight); | ||||
| 			if (!earliestMessage) | ||||
|  |  | |||
|  | @ -72,10 +72,12 @@ function chooseThread() { | |||
| 		loadStreams(); | ||||
| 		setVisibility(); | ||||
| 		document.getElementById('memberlist').replaceChildren( | ||||
| 			...await Promise.all(msg.thread.members.map(async member => | ||||
| 				html.node`<p class='member'>${ | ||||
| 					member.name || (await window.getUser(member.id)).name | ||||
| 				}</p>`)) | ||||
| 			...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])); | ||||
| 				return p; | ||||
| 			})) | ||||
| 		); | ||||
| 	}); | ||||
| } | ||||
|  | @ -111,12 +113,19 @@ function newThread() { | |||
| 	} | ||||
| 
 | ||||
| 	async function addMember() { | ||||
| 		const name = document.getElementById('membername'); | ||||
| 		const name = document.getElementById('newmembername'); | ||||
| 		if (!name.value) | ||||
| 			return; | ||||
| 		let at = name.value.split('@'); | ||||
| 		let url = at[1] || instancediv.instance.url; | ||||
| 		let error = document.getElementById('membererror'); | ||||
| 		let user = await window.getUser('@' + url, at[0]); | ||||
| 		if (!user) { | ||||
| 			error.innerText = 'user not found'; | ||||
| 			return; | ||||
| 		} | ||||
| 		error.innerText = ''; | ||||
| 		user.id = String(user.id); | ||||
| 		if (instancediv.instance.url !== url) { | ||||
| 			user.id += '@' + url; | ||||
| 			user.name += '@' + url; | ||||
|  | @ -178,14 +187,15 @@ function newThread() { | |||
| 			<input type='radio' name='newpermissions' | ||||
| 				id='private_view' value='private_view' /> | ||||
| 			<label for='private_view'>only members can view and post</label><br> | ||||
| 			<label class='heading' for='membername'>members</label> | ||||
| 			<input type='text' id='membername' placeholder='username' onkeydown=${event => { | ||||
| 			<label class='heading' for='newmembername'>members</label> | ||||
| 			<input type='text' id='newmembername' placeholder='username' onkeydown=${event => { | ||||
| 				if (event.key === 'Enter') { | ||||
| 					event.preventDefault(); | ||||
| 					addMember(); | ||||
| 				} | ||||
| 			}} /> | ||||
| 			<button id='addmember' type='button' onclick=${addMember}>add</button> | ||||
| 			<p id='membererror'></p> | ||||
| 			<div id='newmembers'> | ||||
| 				<p class='member'>${members[0].name}</p> | ||||
| 			</div> | ||||
|  | @ -261,7 +271,7 @@ function editThread() { | |||
| 		form['private_post'].checked = true; | ||||
| 	else | ||||
| 		form['private_view'].checked = true; | ||||
| 	document.body.append(form); | ||||
| 	document.getElementById('thread').append(form); | ||||
| 	document.getElementById('edit').textContent = 'cancel'; | ||||
| } | ||||
| 
 | ||||
|  | @ -287,34 +297,33 @@ async function loadThreads(instancediv, select) { | |||
| 		return node; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!document.getElementById('thread')) | ||||
| 		document.body.append(html.node` | ||||
| 			<div id='thread' class='column hidden'> | ||||
| 				<div id='content' class='content'> | ||||
| 					<div id='titlebar'> | ||||
| 						<span id='title'>thread: <strong id='threadname'>meow</strong></span> | ||||
| 						<button id='edit' class='hidden' onclick=${editThread}>edit</button> | ||||
| 					</div> | ||||
| 					<div id='buttonbar'> | ||||
| 						<div id='tabs'> | ||||
| 							<button id='messagetab' class='tab active' onclick=${clickedTab}>messages | ||||
| 							</button><button id='spacetab' class='tab' onclick=${clickedTab}>space | ||||
| 							</button><button id='streamtab' class='tab' onclick=${clickedTab}>streams</button> | ||||
| 						</div> | ||||
| 						<button id='showmembers' onclick=${() => | ||||
| 							document.getElementById('members').classList.toggle('hidden') | ||||
| 						}>members</button> | ||||
| 					</div> | ||||
| 					<div id='message' class='tabcontent'></div> | ||||
| 					<div id='space' class='tabcontent hidden'></div> | ||||
| 					<div id='stream' class='tabcontent hidden'></div> | ||||
| 	let thread = document.getElementById('thread'); | ||||
| 	if (!thread.hasChildNodes()) | ||||
| 		thread.append(html.node` | ||||
| 			<div id='content' class='content'> | ||||
| 				<div id='titlebar'> | ||||
| 					<span id='title'>thread: <strong id='threadname'>meow</strong></span> | ||||
| 					<button id='edit' class='hidden' onclick=${editThread}>edit</button> | ||||
| 				</div> | ||||
| 				<hr class='separator' color='#505050'> | ||||
| 				<div id='members' class='column hidden'> | ||||
| 					<p id='visibility'></p> | ||||
| 					<h4>members</h4> | ||||
| 					<div id='memberlist'> | ||||
| 				<div id='buttonbar'> | ||||
| 					<div id='tabs'> | ||||
| 						<button id='messagetab' class='tab active' onclick=${clickedTab}>messages | ||||
| 						</button><button id='spacetab' class='tab' onclick=${clickedTab}>space | ||||
| 						</button><button id='streamtab' class='tab' onclick=${clickedTab}>streams</button> | ||||
| 					</div> | ||||
| 					<button id='showmembers' onclick=${() => | ||||
| 						document.getElementById('members').classList.toggle('hidden') | ||||
| 					}>members</button> | ||||
| 				</div> | ||||
| 				<div id='message' class='tabcontent'></div> | ||||
| 				<div id='space' class='tabcontent hidden'></div> | ||||
| 				<div id='stream' class='tabcontent hidden'></div> | ||||
| 			</div> | ||||
| 			<hr class='separator' color='#505050'> | ||||
| 			<div id='members' class='column hidden'> | ||||
| 				<p id='visibility'></p> | ||||
| 				<h4>members</h4> | ||||
| 				<div id='memberlist'> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<hr class='separator' color='#505050'>`);
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ create table user ( | |||
| 	id integer primary key asc, | ||||
| 	name text, | ||||
| 	displayname text, | ||||
| 	public boolean default true, | ||||
| 	created timestamp default current_timestamp | ||||
| ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ async function get_history(msg, respond) { | |||
| 		}); | ||||
| 	} | ||||
| 	const messages = (await db.query( | ||||
| 		`select coalesce(displayname, name) as name, user as userid, post.id, content
 | ||||
| 		`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' : ''} | ||||
|  |  | |||
|  | @ -39,8 +39,8 @@ async function create_user(msg, respond) { | |||
| 	} | ||||
| 	// add to db
 | ||||
| 	const insert = await db.query( | ||||
| 		'insert into user (name) values (?) returning id', | ||||
| 		[msg.name] | ||||
| 		'insert into user (name, public) values (?, ?) returning id', | ||||
| 		[msg.name, true] | ||||
| 	); | ||||
| 	await db.query( | ||||
| 		'insert into key (user, pubkey, active) values (?, ?, true)', | ||||
|  | @ -55,9 +55,9 @@ async function create_user(msg, respond) { | |||
| 
 | ||||
| async function getUser(id, name) { | ||||
| 	return (id ? await db.query( | ||||
| 			`select name, id, displayname from user where id = ?`, id) | ||||
| 			`select name, id, displayname, public from user where id = ?`, id) | ||||
| 		: await db.query( | ||||
| 			`select name, id, displayname from user where name = ?`, name) | ||||
| 			`select name, id, displayname, public from user where name = ?`, name) | ||||
| 		).rows[0]; | ||||
| } | ||||
| 
 | ||||
|  | @ -184,6 +184,7 @@ async function authenticate(msg, respond, socket) { | |||
| 			id: user.id, | ||||
| 			name: user.name, | ||||
| 			displayname: user.displayname, | ||||
| 			public: user.public, | ||||
| 			authrequests: Object.entries(user.authrequests).map(authrequest => ({ | ||||
| 				id: authrequest[0], | ||||
| 				time: authrequest[1].time | ||||
|  | @ -221,8 +222,8 @@ async function update_user(msg, respond) { | |||
| 			message: 'user not found' | ||||
| 		}); | ||||
| 	await db.query( | ||||
| 		`update user set displayname = ? where id = ?`, | ||||
| 		[msg.displayname, msg.auth_user.id]); | ||||
| 		`update user set displayname = ?, public = ? where id = ?`, | ||||
| 		[msg.displayname, !!msg.public, msg.auth_user.id]); | ||||
| 	vybe.users[msg.auth_user.id].displayname = msg.displayname; | ||||
| 	respond({ | ||||
| 		success: true | ||||
|  | @ -254,7 +255,8 @@ async function list_users(msg, respond) { | |||
| 		success: true, | ||||
| 		users: (await db.query(` | ||||
| 			select id, name, coalesce(displayname, name) as displayname | ||||
| 			from user`)).rows
 | ||||
| 			from user | ||||
| 			where public = true`)).rows
 | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue