vybe/client/app.js

328 lines
10 KiB
JavaScript
Raw Normal View History

2023-08-30 21:00:17 -07:00
import { render, html } from '/uhtml.js';
2024-04-15 21:52:21 -07:00
import loadMessages from '/message.js';
2024-04-15 14:27:21 -07:00
import loadSpace from '/space.js';
2023-08-30 21:00:17 -07:00
2024-05-12 23:46:05 -07:00
function setVisibility() {
document.getElementById('visibility').innerText = `${
window.currentThread.permissions.everyone.view.value ?
'this thread is visible to everyone' :
'members can view this thread'}
${window.currentThread.permissions.everyone.post.value ?
'anyone can post' :
window.currentThread.permissions.members.post.value ?
'only members can post' : 'some members can post'}`;
}
2024-04-15 21:52:21 -07:00
function chooseThread() {
2024-05-12 23:46:05 -07:00
const edit = document.getElementById('edit');
2024-05-05 00:47:42 -07:00
if (window.currentThread) {
if (window.currentThread.id === this.thread.id)
2024-04-15 14:27:21 -07:00
return;
2024-05-05 00:47:42 -07:00
document.getElementById(`thread${window.currentThread.id}`)
2024-04-15 14:27:21 -07:00
.classList.remove('active');
2024-05-12 23:46:05 -07:00
let editform = document.getElementById('editthread');
if (editform) {
editform.remove();
edit.textContent = 'edit';
}
2024-04-15 14:27:21 -07:00
}
2024-05-12 23:46:05 -07:00
if (this.thread.permissions.admin)
edit.classList.remove('hidden');
else
edit.classList.add('hidden');
2024-04-15 21:52:21 -07:00
document.getElementById('threadname').textContent = this.thread.name;
this.classList.add('active');
2024-05-05 00:47:42 -07:00
window.currentThread = this.thread;
2024-04-15 21:52:21 -07:00
loadMessages();
if (this.thread.permissions.post)
2024-04-15 14:27:21 -07:00
document.getElementById('msginput').classList.remove('hidden');
else
document.getElementById('msginput').classList.add('hidden');
2024-04-15 21:52:21 -07:00
switchTab(document.getElementById(this.tab));
2024-05-05 00:47:42 -07:00
window.emit('get_thread', { thread: this.thread.id }, msg => {
2024-05-12 23:46:05 -07:00
window.currentThread = msg.thread;
setVisibility();
2024-05-05 00:47:42 -07:00
document.getElementById('memberlist').replaceChildren(
...msg.thread.members.map(member =>
html.node`<p class='member'>${member.name}</p>`)
);
});
2023-08-30 21:00:17 -07:00
}
2024-04-15 14:27:21 -07:00
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();
2023-08-30 21:00:17 -07:00
}
2024-05-04 20:13:37 -07:00
async function createThread(event) {
event.preventDefault();
2024-04-15 23:53:54 -07:00
let name = document.getElementById('newthreadname').value;
if (!name) {
2024-05-12 23:46:05 -07:00
document.getElementById('newnameempty').classList.remove('hidden');
2023-12-28 11:54:16 -08:00
return;
}
2023-08-30 21:00:17 -07:00
const perms = document.querySelector(
2024-05-12 23:46:05 -07:00
'input[name="newpermissions"]:checked'
).value;
2023-08-30 21:00:17 -07:00
let permissions;
2024-05-12 23:46:05 -07:00
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 };
2023-08-30 21:00:17 -07:00
// 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 }),
2024-05-12 23:46:05 -07:00
signingKeys: window.keys.priv
2023-08-30 21:00:17 -07:00
});
}
*/
}
2024-05-12 23:46:05 -07:00
window.emit('create_thread',
{ name, permissions, members: window.threadmembers },
msg => {
2024-04-15 21:52:21 -07:00
chooseThread.call(document.getElementById('thread' + msg.id));
// since the form exists, this will perform cleanup
newThread();
2024-04-15 14:27:21 -07:00
document.getElementById('loadmore').classList.add('hidden');
}
);
2023-08-30 21:00:17 -07:00
}
2024-05-12 23:46:05 -07:00
function addMember() {
const name = document.getElementById('membername');
if (!name.value)
return;
window.threadmembers.push({ name: name.value });
document.getElementById('newmembers')
.appendChild(html.node`<p class='member'>${name.value}</p>`);
name.value = '';
}
2023-12-28 12:59:17 -08:00
function newThread() {
2023-08-30 21:00:17 -07:00
let form = document.getElementById('createthread');
if (form) {
form.remove();
2024-04-15 14:27:21 -07:00
document.getElementById('newthread').textContent = 'create';
2024-05-12 23:46:05 -07:00
return;
2023-08-30 21:00:17 -07:00
}
2024-05-12 23:46:05 -07:00
window.threadmembers = [{
name: window.name,
permissions: { admin: 'true' }
}];
document.querySelector('#home + .separator').insertAdjacentElement('afterend', html.node`
<form id='createthread' class='column' onsubmit=${createThread}>
<h3>create thread</h3>
<label for='newthreadname' class='heading'>thread name</label>
<p id='newnameempty' class='hidden'>name cannot be empty</p>
<input type='text' id='newthreadname' />
<p id='permissions'>thread permissions</p>
<input type='radio' name='newpermissions'
id='public' value='public' checked />
<label for='public'>anyone can view and post</label><br>
<input type='radio' name='newpermissions'
id='private_post' value='private_post' />
<label for='private_post'>anyone can view, only members can post</label><br>
<input type='radio' name='newpermissions'
id='private_view' 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=${event => {
if (event.key === 'Enter') {
event.preventDefault();
addMember();
}
}} />
<button id='addmember' type='button' onclick=${addMember}>add</button>
<div id='newmembers'>
<p class='member'>${window.name}</p>
</div>
<br>
<button id='submitthread' type='submit'>create</button>
</form>`);
document.getElementById('newthread').textContent = 'cancel';
}
async function saveThread(event) {
event.preventDefault();
let name = document.getElementById('editthreadname').value;
if (!name) {
document.getElementById('nameempty').classList.remove('hidden');
return;
}
const perms = document.querySelector(
'input[name="permissions"]:checked'
).value;
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 };
// todo: generate key and encrypt
}
window.emit('edit_thread',
{ id: window.currentThread.id, name, permissions },
msg => {
if (!msg.success) {
console.log('edit_thread failed: ', msg.message);
return;
}
editThread();
});
}
function editThread() {
let form = document.getElementById('editthread');
if (form) {
form.remove();
document.getElementById('edit').textContent = 'edit';
return;
}
form = html.node`
<form id='editthread' class='column' onsubmit=${saveThread}>
<label for='editthreadname' class='heading'>thread name</label>
<input type='text' id='editthreadname' />
<p id='nameempty' class='hidden'>name cannot be empty</p>
<p id='permissions'>thread permissions</p>
<input type='radio' name='permissions'
id='public' value='public' />
<label for='public'>anyone can view and post</label><br>
<input type='radio' name='permissions'
id='private_post' value='private_post' />
<label for='private_post'>anyone can view, only members can post</label><br>
<input type='radio' name='permissions'
id='private_view' value='private_view' />
<label for='private_view'>only members can view and post</label><br>
<br>
<button id='savethread'>save</button>
</form>`;
form['editthreadname'].value = window.currentThread.name;
if (window.currentThread.permissions.everyone.post.value)
form['public'].checked = true;
else if (window.currentThread.permissions.everyone.view.value)
form['private_post'].checked = true;
else
form['private_view'].checked = true;
document.querySelector('#thread').append(form);
document.getElementById('edit').textContent = 'cancel';
2023-08-30 21:00:17 -07:00
}
2024-04-15 14:27:21 -07:00
function clickedTab(event) {
switchTab(event.target);
2024-05-05 00:47:42 -07:00
document.getElementById(`thread${window.currentThread.id}`).tab = event.target.id;
2023-12-28 11:54:16 -08:00
}
2024-05-04 20:13:37 -07:00
// main app html
2024-04-29 21:05:45 -07:00
document.body.append(html.node`
2024-04-28 22:55:54 -07:00
<div id='home' class='column'>
<div id='threads'>
<h3>vybe</h3>
<h4>threads</h4>
<div id='threadlist'>loading...</div>
<button id='newthread' onclick=${newThread}>create</button>
</div>
<div id='user' onclick=${() =>
document.getElementById('profile').classList.toggle('hidden')
}>${window.name}</div>
</div>
2024-05-05 00:47:42 -07:00
<hr class='separator' color='#505050'>
2024-05-12 23:46:05 -07:00
<!-- create thread column goes here -->
<hr class='separator' color='#505050'>
2024-04-28 22:55:54 -07:00
<div id='profile' class='column hidden'>
2024-04-29 21:05:45 -07:00
<p><strong>authentication requests</strong></p>
2024-04-28 22:55:54 -07:00
<div id='authrequests'></div>
2023-12-28 11:54:16 -08:00
</div>
2024-05-05 00:47:42 -07:00
<hr class='separator' color='#505050'>
2024-04-15 14:27:21 -07:00
<div id='thread' class='column'>
2024-05-04 20:13:37 -07:00
<div id='content'>
2024-05-14 20:31:52 -07:00
<div id='titlebar'>
<span id='title'>thread: <strong id='threadname'>meow</strong></span>
2024-05-12 23:46:05 -07:00
<button id='edit' class='hidden' onclick=${editThread}>edit</button>
2024-05-04 20:13:37 -07:00
</div>
2024-05-14 20:31:52 -07:00
<div id='buttonbar'>
2024-05-04 20:13:37 -07:00
<div id='tabs'>
<button id='messagetab' class='tab active' onclick=${clickedTab}>messages
</button><button id='spacetab' class='tab' onclick=${clickedTab}>space</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>
2023-08-30 21:00:17 -07:00
</div>
2024-05-05 00:47:42 -07:00
<hr class='separator' color='#505050'>
2024-05-04 20:13:37 -07:00
<div id='members' class='hidden'>
<p id='visibility'></p>
2024-05-05 00:47:42 -07:00
<p><strong>members</strong></p>
2024-05-04 20:13:37 -07:00
<div id='memberlist'>
</div>
2024-02-19 22:31:01 -08:00
</div>
2024-05-12 23:46:05 -07:00
<hr class='separator' color='#505050'>
2023-12-28 11:54:16 -08:00
</div>
`);
2023-08-30 21:00:17 -07:00
2024-04-15 21:52:21 -07:00
function makeThread(thread) {
let node = html.node`
<div class='thread' onclick=${chooseThread}>${
thread.name
}</div>`;
node.id = 'thread' + thread.id;
node.thread = thread;
node.tab = 'messagetab';
return node;
}
2024-05-12 23:46:05 -07:00
window.socket.on('thread', thread => {
let el = document.getElementById('thread' + thread.id);
if (el) {
el.thread = thread;
el.textContent = thread.name;
if (window.currentThread.id === thread.id) {
Object.assign(window.currentThread, thread);
document.getElementById('threadname').textContent = thread.name;
setVisibility();
}
return;
}
2024-04-15 21:52:21 -07:00
document.getElementById('threadlist').prepend(makeThread(thread));
2023-08-30 21:00:17 -07:00
});
2024-04-15 14:27:21 -07:00
window.emit('list_threads', {}, msg => {
2024-05-05 00:47:42 -07:00
const threadlist = document.getElementById('threadlist');
threadlist.replaceChildren(...msg.threads.map(makeThread));
2024-04-15 21:52:21 -07:00
chooseThread.call(threadlist.firstChild);
2023-08-30 21:00:17 -07:00
});
2024-04-28 22:55:54 -07:00
window.socket.on('authrequest', (msg, respond) => {
const div = html.node`
<div class='authrequest'>
<button onclick=${() => {
respond(true);
div.remove();
}}>approve</button>
session <strong>${msg.id}</strong>
</div>`;
document.getElementById('authrequests').append(div);
setTimeout(() => div.remove(), Date.now() - msg.time + 60000 * 5);
});