vybe/client/app.js

328 lines
9.6 KiB
JavaScript
Raw Normal View History

2023-08-30 21:00:17 -07:00
import { render, html } from '/uhtml.js';
2024-06-12 03:00:50 -07:00
import loadThreads from '/thread.js';
2023-08-30 21:00:17 -07:00
2024-06-14 22:24:32 -07:00
function saveProfile(e) {
2024-06-12 03:00:50 -07:00
let displayname = document.getElementById('newname').value;
window.emit('update_user', {
2024-06-14 22:24:32 -07:00
displayname,
public: document.getElementById('profilepublic').checked
2024-06-12 03:00:50 -07:00
}, msg => {
if (!msg.success) {
console.log('update_user error: ', msg.message);
document.getElementById('savename').classList.remove('hidden');
return;
}
2024-06-12 03:00:50 -07:00
window.displayname = displayname;
document.getElementById('savename').classList.add('hidden');
2024-06-14 22:24:32 -07:00
});
2023-08-30 21:00:17 -07:00
}
2024-06-12 03:00:50 -07:00
window.connectInstance = async url => {
let instance = window.instances[url];
let connected;
function connecting(resolve, reject) {
instance.socket.on('connect', resolve);
instance.socket.on('connect_error', error => {
console.log(`error connecting to ${url}: ${error}`);
if (connected)
return;
instance.socket.disconnect();
reject(error.message);
});
2023-12-28 11:54:16 -08:00
}
2024-06-12 03:00:50 -07:00
if (instance) {
if (!instance.socket?.connected) {
if (instance.socket)
instance.socket.connect();
else
instance.socket = io(url);
await new Promise(connecting);
2023-08-30 21:00:17 -07:00
}
}
2024-06-12 03:00:50 -07:00
else {
instance = window.instances[url] = {
socket: io(url),
url
};
await new Promise(connecting);
2023-08-30 21:00:17 -07:00
}
2024-06-12 03:00:50 -07:00
connected = true;
return instance;
};
2024-05-12 23:46:05 -07:00
2024-06-12 03:00:50 -07:00
window.getUser = async (id, name) => {
let instance = window.instancelist[0];
if (id) {
id = id.split('@');
instance = await connectInstance(id[1]);
id = id[0];
2024-05-12 23:46:05 -07:00
}
2024-06-12 03:00:50 -07:00
return new Promise((resolve, reject) =>
instance.socket.emit('get_user', { id, name }, msg => {
if (!msg.success)
2024-06-14 22:24:32 -07:00
if (msg.message === 'user not found')
return resolve();
else
return reject(msg.message);
2024-06-12 03:00:50 -07:00
resolve(msg.user);
})
);
};
2024-06-19 22:57:57 -07:00
window.makeUser = (user, url, context) => {
let removable = context === 'user' ? false :
window.currentThread?.permissions.admin && !user.permissions?.admin &&
(context === 'member' ? true : Array.from(document.getElementById('memberlist').children)
.find(p => p.children[0].user?.id == user.id));
2024-06-14 22:24:32 -07:00
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}`;
2024-06-17 19:44:11 -07:00
if (removable)
document.getElementById('removemember').classList.remove('hidden');
else
document.getElementById('removemember').classList.add('hidden');
2024-06-14 22:24:32 -07:00
}}>${user.displayname}</span>`;
span.user = user;
return span;
};
2024-06-12 03:00:50 -07:00
async function authenticateInstance(div, select) {
return new Promise((resolve, reject) =>
div.instance.socket.emit('authenticate', {
name: window.name,
id: window.id + '@' + location.host,
message: window.signedMessage,
pubkey: window.keys.armored.publicKey
}, msg => {
2024-05-12 23:46:05 -07:00
if (!msg.success) {
2024-06-12 03:00:50 -07:00
reject(`${div.instance.url} authentication failed: ${msg.message}`);
2024-05-12 23:46:05 -07:00
return;
}
2024-06-12 03:00:50 -07:00
div.instance.id = msg.instance.id;
div.id = 'instance' + msg.instance.id;
div.instance.emit = (event, data, callback) =>
div.instance.socket.emit(event, {
...data,
__session: window.session,
}, callback);
loadThreads(div, select);
resolve();
}));
2024-05-12 23:46:05 -07:00
}
2024-06-12 03:00:50 -07:00
function expandInstance(event) {
this.parentElement.parentElement.children['threads'].classList.toggle('hidden');
this.children[0].classList.toggle('collapsed');
2023-08-30 21:00:17 -07:00
}
2024-06-12 21:52:23 -07:00
function instanceClicked(event) {
let instance = this.parentElement.parentElement.instance;
let div = document.getElementById('instance');
if (div.classList.contains('hidden')) {
let disconnect = document.getElementById('disconnect');
if (window.instancelist[0] === instance)
disconnect.classList.add('hidden');
else
disconnect.classList.remove('hidden');
document.getElementById('instancename').textContent = instance.url;
let userlist = document.getElementById('userlist');
userlist.innerHTML = '';
2024-06-16 00:02:15 -07:00
instance.emit?.('list_users', {}, msg =>
2024-06-14 22:24:32 -07:00
userlist.replaceChildren(...msg.users.map(user => {
let p = document.createElement('p');
2024-06-19 22:57:57 -07:00
p.append(window.makeUser(user, instance.url, 'user'));
2024-06-14 22:24:32 -07:00
return p;
}))
2024-06-12 21:52:23 -07:00
);
div.instance = instance;
}
div.classList.toggle('hidden');
}
2024-06-12 03:00:50 -07:00
function saveInstances() {
localStorage.setItem('instances', JSON.stringify(
window.instancelist.map(i => ({
id: i.id,
url: i.url
}))
));
2023-12-28 11:54:16 -08:00
}
2024-06-12 03:00:50 -07:00
async function addInstance() {
this.textContent = this.textContent.trim().toLowerCase();
let instancediv = this.parentElement.parentElement;
if (!this.textContent)
return instancediv.remove();
2024-06-12 21:52:23 -07:00
if (window.instancelist.find(i => i.url === this.textContent))
2024-06-12 03:00:50 -07:00
return instancediv.remove();
2024-06-12 21:52:23 -07:00
let instance = await connectInstance(this.textContent);
2024-06-12 03:00:50 -07:00
instancediv.instance = instance;
await authenticateInstance(instancediv, true);
this.contentEditable = false;
/* expander is initially just a span with a non-space space
in it to make the title span style work */
let expander = this.parentElement.children[0];
expander.classList.add('expander');
expander.onclick = expandInstance;
expander.innerHTML = `<span class='arrow'>◿</span>`;
window.instancelist.push(instance);
saveInstances();
this.onclick = instanceClicked;
}
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-06-12 21:52:23 -07:00
<div id='profile' class='column hidden'>
<p><strong>profile</strong></p>
<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')
2024-06-14 22:24:32 -07:00
saveProfile();
2024-06-12 21:52:23 -07:00
else
document.getElementById('savename').classList.remove('hidden');
}}>
2024-06-14 22:24:32 -07:00
<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>
2024-06-19 22:57:57 -07:00
<label class='heading'>authentication requests:</label>
2024-06-12 21:52:23 -07:00
<div id='authrequests'></div>
</div>
<hr class='separator' color='#505050'>
2024-04-28 22:55:54 -07:00
<div id='home' class='column'>
2024-06-19 22:57:57 -07:00
<h3>vybe</h3>
<p id='instances'>instances:<button onclick=${e => {
let div = html.node`
<div>
<div class='instancetitle'>
<span></span>
<span onblur=${addInstance} onkeydown=${function(event) {
if (event.key === 'Enter') {
event.preventDefault();
addInstance.call(this);
}
}} class='title' contenteditable='true'>
</span>
</div>
</div>`;
document.getElementById('instancelist').append(div);
div.children[0].children[1].focus();
}}>add</button></p>
2024-06-12 03:00:50 -07:00
<div id='instancelist'>
2024-04-28 22:55:54 -07:00
</div>
2024-06-19 22:57:57 -07:00
<div class='content'><!-- empty flex div to push instancelist up -->
</div>
<div id='user' onclick=${e =>
2024-04-28 22:55:54 -07:00
document.getElementById('profile').classList.toggle('hidden')
}>${window.name}</div>
</div>
2024-05-05 00:47:42 -07:00
<hr class='separator' color='#505050'>
2024-06-12 21:52:23 -07:00
<div id='instance' class='column hidden'>
<div class='content'>
<p>instance: <strong id='instancename'></strong></p>
<h4>users</h4>
<div id='userlist'>
</div>
</div>
2024-06-17 19:44:11 -07:00
<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>
2023-12-28 11:54:16 -08:00
</div>
2024-05-05 00:47:42 -07:00
<hr class='separator' color='#505050'>
2024-06-12 21:52:23 -07:00
<!-- create thread column goes here -->
<hr class='separator' color='#505050'>
2024-06-14 22:24:32 -07:00
<div id='thread' class='column hidden'></div>
<hr class='separator' color='#505050'>
<div id='member' class='column hidden'>
2024-06-17 19:44:11 -07:00
<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>
2024-06-14 22:24:32 -07:00
<p>
2024-06-17 19:44:11 -07:00
<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>
2024-06-14 22:24:32 -07:00
</p>
</div>
2023-12-28 11:54:16 -08:00
`);
2023-08-30 21:00:17 -07:00
2024-06-12 03:00:50 -07:00
for (let i = 0; i < instancelist.length; ++i) {
let instance = instancelist[i];
let div = html.node`
<div>
2024-06-12 21:52:23 -07:00
<div class='instancetitle'>
2024-06-12 03:00:50 -07:00
<span class='expander' onclick=${expandInstance}>
<span class='arrow'></span>
</span>
<span class='title' onclick=${instanceClicked}>${instance.url}</span>
</div>
</div>`;
div.id = 'instance' + instance.id;
div.instance = instance;
document.getElementById('instancelist').append(div);
if (i === 0)
loadThreads(div, true);
else {
await connectInstance(instance.url);
authenticateInstance(div);
}
2024-04-15 21:52:21 -07:00
}
2024-06-12 03:00:50 -07:00
document.getElementById('newname').value = window.displayname;
2023-08-30 21:00:17 -07:00
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);
export {
authRequest
};