268 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
| import { render, html } from '/uhtml.js';
 | |
| import loadSpace from '/space.js';
 | |
| 
 | |
| window.currentThreadId = 1;
 | |
| 
 | |
| function chooseThread(thread) {
 | |
| 	if (window.currentThreadId) {
 | |
| 		if (window.currentThreadId === thread.id)
 | |
| 			return;
 | |
| 		document.getElementById(`thread${window.currentThreadId}`)
 | |
| 			.classList.remove('active');
 | |
| 	}
 | |
| 	const el = document.getElementById(`thread${thread.id}`);
 | |
| 	el.classList.add('active');
 | |
| 	window.currentThreadId = thread.id;
 | |
| 	window.earliestMessage = null;
 | |
| 	document.getElementById('threadname').textContent = thread.name;
 | |
| 	if (thread.permissions.post)
 | |
| 		document.getElementById('msginput').classList.remove('hidden');
 | |
| 	else
 | |
| 		document.getElementById('msginput').classList.add('hidden');
 | |
| 	switchTab(document.getElementById(el.tab));
 | |
| 	loadMessages();
 | |
| 	if (el.tab === 'spacetab')
 | |
| 		loadSpace();
 | |
| }
 | |
| 
 | |
| function loadMessages() {
 | |
| 	document.getElementById('messages').innerHTML = '';
 | |
| 	window.emit(
 | |
| 		'get_history',
 | |
| 		{
 | |
| 			before: window.earliestMessage,
 | |
| 			thread: window.currentThreadId,
 | |
| 		},
 | |
| 		msg => {
 | |
| 			if (msg.messages.length > 0) {
 | |
| 				window.earliestMessage = msg.messages[msg.messages.length - 1].id;
 | |
| 				for (let message of msg.messages)
 | |
| 					document.getElementById('messages').prepend(html.node`
 | |
| 					<div class='message'>
 | |
| 						<strong>${message.name}: </strong>
 | |
| 						${message.message}
 | |
| 					</div>`);
 | |
| 			}
 | |
| 			if (!msg.more)
 | |
| 				document.getElementById('loadmore').classList.add('hidden');
 | |
| 			else
 | |
| 				document.getElementById('loadmore').classList.remove('hidden');
 | |
| 		}
 | |
| 	);
 | |
| }
 | |
| 
 | |
| function addThread(thread, top) {
 | |
| 	let node = html.node`
 | |
| 		<div class='thread' onclick=${() => chooseThread(thread)}>${
 | |
| 		thread.name
 | |
| 	}</div>`;
 | |
| 	node.id = `thread${thread.id}`;
 | |
| 	node.tab = 'messagetab';
 | |
| 	document.getElementById('threadlist')[top ? 'prepend' : 'appendChild'](node);
 | |
| }
 | |
| 
 | |
| function switchTab(tab) {
 | |
| 	for (let tab of document.querySelectorAll('.tab'))
 | |
| 		tab.classList.remove('active');
 | |
| 	for (let tab of document.querySelectorAll('.tabcontent'))
 | |
| 		tab.classList.add('hidden');
 | |
| 	tab.classList.add('active');
 | |
| 	document
 | |
| 		.getElementById(tab.id.slice(0, -3))
 | |
| 		.classList.remove('hidden');
 | |
| 	if (tab.id === 'spacetab')
 | |
| 		loadSpace();
 | |
| }
 | |
| 
 | |
| function addMember() {
 | |
| 	const name = document.getElementById('membername').value;
 | |
| 	window.threadmembers.push(name);
 | |
| 	document
 | |
| 		.getElementById('memberlist')
 | |
| 		.appendChild(html.node`<p class='member'>${name}</p>`);
 | |
| 	document.getElementById('membername').value = '';
 | |
| }
 | |
| 
 | |
| async function createThread(e) {
 | |
| 	e.preventDefault();
 | |
| 	let name = document.getElementById('newthreadname');
 | |
| 	if (!name.value) {
 | |
| 		name.insertAdjacentHTML('afterend', `<p>name cannot be empty</p>`);
 | |
| 		return;
 | |
| 	}
 | |
| 	let members = window.threadmembers.map(name => ({ name }));
 | |
| 	const perms = document.querySelector(
 | |
| 		'input[name="permissions"]:checked'
 | |
| 	).value;
 | |
| 	if (perms === 'private_view')
 | |
| 		members = (
 | |
| 			await new Promise(resolve =>
 | |
| 				window.emit('get_keys', { names: window.threadmembers }, resolve)
 | |
| 			)
 | |
| 		).keys;
 | |
| 	let permissions;
 | |
| 	if (perms === 'public') {
 | |
| 		permissions = {
 | |
| 			view_limited: false,
 | |
| 			post_limited: false
 | |
| 		};
 | |
| 	} else if (perms === 'private_post') {
 | |
| 		permissions = {
 | |
| 			view_limited: false,
 | |
| 			post_limited: true
 | |
| 		};
 | |
| 	} else if (perms === 'private_view') {
 | |
| 		permissions = {
 | |
| 			view_limited: true,
 | |
| 			post_limited: true
 | |
| 		};
 | |
| 		// generate key
 | |
| 		/* wip
 | |
| 		var buf = new Uint8Array(32);
 | |
| 		crypto.getRandomValues(buf);
 | |
| 		const key = aesjs.utils.hex.fromBytes(Array.from(buf));
 | |
| 		// sign it to each of the members
 | |
| 		for (let i = 0; i < newmembers.length; i++) {
 | |
| 			const member = newmembers[i];
 | |
| 			const sig = await openpgp.encrypt({
 | |
| 				message: await openpgp.createMessage({ text: key }),
 | |
| 				signingKeys: window.keys.priv,
 | |
| 			});
 | |
| 		}
 | |
| 		*/
 | |
| 	}
 | |
| 	window.emit(
 | |
| 		'create_thread',
 | |
| 		{
 | |
| 			name: name.value,
 | |
| 			permissions,
 | |
| 			members
 | |
| 		},
 | |
| 		msg => {
 | |
| 			chooseThread({
 | |
| 				name: name.value,
 | |
| 				id: msg.id,
 | |
| 				permissions: {
 | |
| 					is_member: true,
 | |
| 					view: true,
 | |
| 					post: true
 | |
| 				}
 | |
| 			});
 | |
| 			// since the form exists, this will perform cleanup
 | |
| 			newThread();
 | |
| 			document.getElementById('loadmore').classList.add('hidden');
 | |
| 			document.getElementById('msginput').classList.remove('hidden');
 | |
| 		}
 | |
| 	);
 | |
| }
 | |
| 
 | |
| function sendMessage(e) {
 | |
| 	e.preventDefault();
 | |
| 	const msg = document.getElementById('msg').value;
 | |
| 	if (!msg)
 | |
| 		return;
 | |
| 	window.emit('send_message', {
 | |
| 		message: msg,
 | |
| 		thread: window.currentThreadId
 | |
| 	});
 | |
| 	document.getElementById('msg').value = '';
 | |
| }
 | |
| 
 | |
| function newThread() {
 | |
| 	let form = document.getElementById('createthread');
 | |
| 	if (form) {
 | |
| 		form.remove();
 | |
| 		document.getElementById('newthread').textContent = 'create';
 | |
| 	} else {
 | |
| 		window.threadmembers = [window.name];
 | |
| 		document.getElementById('threads').insertAdjacentElement('afterend', html.node`
 | |
| 			<form id='createthread' class='column' onsubmit=${createThread}>
 | |
| 				<h3>create thread</h3>
 | |
| 				<label for='newthreadname' class='heading'>thread name</label>
 | |
| 				<input type='text' id='newthreadname' />
 | |
| 				<p id='permissions'>thread permissions</p>
 | |
| 				<input type='radio' id='public' name='permissions' value='public' checked />
 | |
| 				<label for='public'>anyone can view and post</label><br />
 | |
| 				<input type='radio' id='private_post'
 | |
| 					name='permissions' value='private_post'
 | |
| 				/>
 | |
| 				<label for='private_post'>anyone can view, only members can post</label><br/>
 | |
| 				<input type='radio' id='private_view'
 | |
| 					name='permissions' value='private_view'
 | |
| 				/>
 | |
| 				<label for='private_view'>only members can view and post</label
 | |
| 				><br /><br />
 | |
| 				<label class='heading' for='membername'>members</label>
 | |
| 				<input type='text' id='membername' placeholder='username' onkeydown=${(e) => {
 | |
| 					if (e.key == 'Enter') {
 | |
| 						e.preventDefault();
 | |
| 						addMember();
 | |
| 					}
 | |
| 				}}/>
 | |
| 				<button id='addmember' onclick=${addMember}>add</button>
 | |
| 				<div id='memberlist'>
 | |
| 					<p class='member'>${window.name}</p>
 | |
| 				</div>
 | |
| 				<br />
 | |
| 				<button id='submitthread' type='submit'>create</button>
 | |
| 			</form>
 | |
| 		`
 | |
| 		);
 | |
| 		document.getElementById('newthread').textContent = 'cancel';
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function clickedTab(event) {
 | |
| 	switchTab(event.target);
 | |
| 	document.getElementById(`thread${window.currentThreadId}`).tab = event.target.id;
 | |
| }
 | |
| 
 | |
| render(document.body, html`
 | |
| 	<div id='threads' class='column'>
 | |
| 		<h3>vybe</h3>
 | |
| 		<h4>threads</h4>
 | |
| 		<div id='threadlist'>loading...</div>
 | |
| 		<button id='newthread' onclick=${newThread}>create</button>
 | |
| 	</div>
 | |
| 	<hr class='separator' color='#666'>
 | |
| 	<div id='thread' class='column'>
 | |
| 		<div id='title'>
 | |
| 			thread: <strong id='threadname'>meow</strong>
 | |
| 		</div>
 | |
| 		<div id='tabs'>
 | |
| 			<button id='messagetab' class='tab active' onclick=${clickedTab}>messages</button><button id='spacetab' class='tab' onclick=${clickedTab}>space</button>
 | |
| 		</div>
 | |
| 		<div id='message' class='tabcontent'>
 | |
| 			<button id='loadmore' class='hidden' onclick=${loadMessages}>
 | |
| 			load more messages
 | |
| 			</button>
 | |
| 			<div id='messages'></div>
 | |
| 			<form id='msginput' onsubmit=${sendMessage}>
 | |
| 				<input type='text' placeholder='write a message...' id='msg' />
 | |
| 				<button type='submit' id='sendmsg'>send</button>
 | |
| 			</form>
 | |
| 		</div>
 | |
| 		<div id='space' class='tabcontent hidden'></div>
 | |
| 	</div>
 | |
| `);
 | |
| 
 | |
| window.socket.on('new_message', msg => {
 | |
| 	if (msg.thread !== window.currentThreadId)
 | |
| 		return;
 | |
| 	document.getElementById('messages').appendChild(html.node`
 | |
| 		<div class='message'>
 | |
| 			<strong>${msg.name}: </strong>
 | |
| 			${msg.message}
 | |
| 		</div>`);
 | |
| 	if (!window.earliestMessage)
 | |
| 		window.earliestMessage = msg.id;
 | |
| });
 | |
| window.socket.on('new_thread', thread => addThread(thread, true));
 | |
| 
 | |
| window.emit('list_threads', {}, msg => {
 | |
| 	document.getElementById('threadlist').innerHTML = '';
 | |
| 	for (let thread of msg.threads)
 | |
| 		addThread(thread);
 | |
| 	chooseThread(msg.threads[0]);
 | |
| });
 |