386 lines
12 KiB
JavaScript
386 lines
12 KiB
JavaScript
import { render, html } from '/uhtml.js';
|
|
import loadMessages from '/message.js';
|
|
import loadSpace from '/space.js';
|
|
import loadStreams from '/stream.js';
|
|
|
|
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'}`;
|
|
}
|
|
|
|
function chooseThread() {
|
|
const edit = document.getElementById('edit');
|
|
if (window.currentThread) {
|
|
document.getElementById(`thread${window.currentThread.id}`).classList.remove('active');
|
|
let editform = document.getElementById('editthread');
|
|
if (editform) {
|
|
editform.remove();
|
|
edit.textContent = 'edit';
|
|
}
|
|
if (window.currentThread.id === this.thread.id) {
|
|
document.getElementById('thread').classList.add('hidden');
|
|
window.currentThread = null;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
document.getElementById('thread').classList.remove('hidden');
|
|
if (this.thread.permissions.admin)
|
|
edit.classList.remove('hidden');
|
|
else
|
|
edit.classList.add('hidden');
|
|
document.getElementById('threadname').textContent = this.thread.name;
|
|
this.classList.add('active');
|
|
window.currentThread = this.thread;
|
|
loadStreams();
|
|
if (this.tab)
|
|
switchTab(document.getElementById(this.tab));
|
|
else // load first tab that has any content
|
|
loadMessages(true, messages => {
|
|
if (messages.length)
|
|
switchTab(document.getElementById(this.tab = 'messagetab'));
|
|
else
|
|
loadSpace(spans => {
|
|
if (spans.length)
|
|
switchTab(document.getElementById(this.tab = 'spacetab'));
|
|
else if (window.currentThread.streams.length)
|
|
switchTab(document.getElementById(this.tab = 'streamtab'));
|
|
else
|
|
switchTab(document.getElementById(
|
|
this.tab = spans.length ? 'spacetab' :
|
|
window.currentThread.streams.length ? 'streamtab' : 'messagetab'));
|
|
});
|
|
});
|
|
window.emit('get_thread', { thread: this.thread.id }, msg => {
|
|
window.currentThread = msg.thread;
|
|
setVisibility();
|
|
document.getElementById('memberlist').replaceChildren(
|
|
...msg.thread.members.map(member =>
|
|
html.node`<p class='member'>${member.name}</p>`)
|
|
);
|
|
});
|
|
}
|
|
|
|
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 === 'messagetab')
|
|
loadMessages(true);
|
|
else if (tab.id === 'spacetab')
|
|
loadSpace();
|
|
}
|
|
|
|
async function createThread(event) {
|
|
event.preventDefault();
|
|
let name = document.getElementById('newthreadname').value;
|
|
if (!name) {
|
|
document.getElementById('newnameempty').classList.remove('hidden');
|
|
return;
|
|
}
|
|
const perms = document.querySelector(
|
|
'input[name="newpermissions"]: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 };
|
|
// 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, permissions, members: window.threadmembers },
|
|
msg => {
|
|
chooseThread.call(document.getElementById('thread' + msg.id));
|
|
// since the form exists, this will perform cleanup
|
|
newThread();
|
|
document.getElementById('loadmore').classList.add('hidden');
|
|
}
|
|
);
|
|
}
|
|
|
|
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 = '';
|
|
}
|
|
|
|
function newThread() {
|
|
let form = document.getElementById('createthread');
|
|
if (form) {
|
|
form.remove();
|
|
document.getElementById('newthread').textContent = 'create';
|
|
return;
|
|
}
|
|
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>
|
|
<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';
|
|
}
|
|
|
|
function clickedTab(event) {
|
|
switchTab(event.target);
|
|
document.getElementById(`thread${window.currentThread.id}`).tab = event.target.id;
|
|
}
|
|
|
|
function changeName(e) {
|
|
let displayname = document.getElementById('newname').value;
|
|
window.emit('update_user', {
|
|
displayname
|
|
}, msg => {
|
|
if (!msg.success) {
|
|
console.log('update_user error: ', msg.message);
|
|
document.getElementById('savename').classList.remove('hidden');
|
|
return;
|
|
}
|
|
window.displayname = displayname;
|
|
document.getElementById('savename').classList.add('hidden');
|
|
})
|
|
}
|
|
|
|
// main app html
|
|
document.body.append(html.node`
|
|
<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>
|
|
<hr class='separator' color='#505050'>
|
|
<!-- create thread column goes here -->
|
|
<hr class='separator' color='#505050'>
|
|
<div id='profile' class='column hidden'>
|
|
<label class='heading'>display name</label>
|
|
<input id='newname' onkeyup=${function(event) {
|
|
if (window.displayname === this.value)
|
|
document.getElementById('savename').classList.add('hidden');
|
|
else if (event.key === 'Enter')
|
|
changeName();
|
|
else
|
|
document.getElementById('savename').classList.remove('hidden');
|
|
}}>
|
|
<button class='hidden' id='savename' onclick=${changeName}>save</button>
|
|
<label class='heading'>authentication requests</label>
|
|
<div id='authrequests'></div>
|
|
</div>
|
|
<hr class='separator' color='#505050'>
|
|
<div id='thread' class='column'>
|
|
<div id='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>
|
|
</div>
|
|
<hr class='separator' color='#505050'>
|
|
<div id='members' class='hidden'>
|
|
<p id='visibility'></p>
|
|
<p><strong>members</strong></p>
|
|
<div id='memberlist'>
|
|
</div>
|
|
</div>
|
|
<hr class='separator' color='#505050'>
|
|
</div>
|
|
`);
|
|
|
|
document.getElementById('newname').value = window.displayname;
|
|
|
|
function makeThread(thread) {
|
|
let node = html.node`
|
|
<div class='thread' onclick=${chooseThread}>${
|
|
thread.name
|
|
}</div>`;
|
|
node.id = 'thread' + thread.id;
|
|
node.thread = thread;
|
|
return node;
|
|
}
|
|
|
|
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;
|
|
}
|
|
document.getElementById('threadlist').prepend(makeThread(thread));
|
|
});
|
|
|
|
function authRequest(authrequest) {
|
|
const p = html.node`
|
|
<p>
|
|
<button onclick=${() => {
|
|
window.emit('authorize_key', {
|
|
id: authrequest.id
|
|
}, msg => {
|
|
if (!msg.success)
|
|
console.log('authorize_key failed: ', msg.message);
|
|
});
|
|
p.remove();
|
|
}}>approve</button>
|
|
session <strong>${authrequest.id}</strong>
|
|
</p>`;
|
|
document.getElementById('authrequests').append(p);
|
|
setTimeout(() => p.remove(), Date.now() - authrequest.time + 60000 * 5);
|
|
}
|
|
|
|
window.socket.on('authrequest', authRequest);
|
|
|
|
window.emit('list_threads', {}, msg => {
|
|
const threadlist = document.getElementById('threadlist');
|
|
threadlist.replaceChildren(...msg.threads.map(makeThread));
|
|
chooseThread.call(threadlist.firstChild);
|
|
});
|
|
|
|
export {
|
|
authRequest
|
|
};
|