bugfixes and style improvements

main
june moretz 2023-12-28 14:59:17 -06:00
parent 0b830174d3
commit 05bbdc0d60
5 changed files with 524 additions and 455 deletions

View File

@ -1,23 +1,33 @@
import { render, html } from '/uhtml.js';
import { render, html } from "/uhtml.js";
window.currentThreadId = 1;
function chooseThread(thread) {
if (window.currentThreadId)
document.getElementById(`thread${window.currentThreadId}`).classList.remove('selected');
document.getElementById(`thread${thread.id}`).classList.add('selected');
document
.getElementById(`thread${window.currentThreadId}`)
.classList.remove("active");
document.getElementById(`thread${thread.id}`).classList.add("active");
window.currentThreadId = thread.id;
window.earliestMessage = null;
document.getElementById("messages").innerHTML = "";
document.getElementById("threadname").textContent = thread.name;
if (!thread.permissions.post) {
document.getElementById("msginput").classList.add("hidden");
} else {
document.getElementById("msginput").classList.remove("hidden");
}
loadMessages();
}
function loadMessages() {
window.emit("get_history", {
window.emit(
"get_history",
{
before: window.earliestMessage,
thread: window.currentThreadId,
}, msg => {
},
(msg) => {
if (msg.messages.length > 0) {
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
for (let message of msg.messages)
@ -29,30 +39,26 @@ function loadMessages() {
}
if (!msg.more)
document.getElementById("loadmore").classList.add("hidden");
else
document.getElementById("loadmore").classList.remove("hidden");
});
else document.getElementById("loadmore").classList.remove("hidden");
}
);
}
function addThread(thread) {
function addThread(thread, top) {
let node = html.node`
<div class='thread' onclick=${() => {
chooseThread(thread);
if (!thread.permissions.post) {
document.getElementById("msginput").classList.add("hidden");
} else {
document.getElementById("msginput").classList.remove("hidden");
}
}}>${thread.name}</div>`;
<button class='thread tab' onclick=${() => chooseThread(thread)}>${
thread.name
}</button>`;
node.id = `thread${thread.id}`;
document.getElementById("threadlist").appendChild(node);
document.getElementById("threadlist")[top ? "prepend" : "appendChild"](node);
}
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("memberlist")
.appendChild(html.node`<p class='member'>${name}</p>`);
document.getElementById("membername").value = "";
}
@ -60,31 +66,34 @@ async function createThread(e) {
e.preventDefault();
let name = document.getElementById("newthreadname");
if (!name.value) {
name.insertAdjacentHTML('afterend', `<p>name cannot be empty</p>`);
name.insertAdjacentHTML("afterend", `<p>name cannot be empty</p>`);
return;
}
let members = window.threadmembers.map(name => ({ name }));
let members = window.threadmembers.map((name) => ({ name }));
const perms = document.querySelector(
'input[name="permissions"]:checked').value;
'input[name="permissions"]:checked'
).value;
if (perms === "private_view")
members = (await new Promise(resolve =>
members = (
await new Promise((resolve) =>
window.emit("get_keys", { names: window.threadmembers }, resolve)
)).keys;
)
).keys;
let permissions;
if (perms === "public") {
permissions = {
view_limited: false,
post_limited: false
post_limited: false,
};
} else if (perms === "private_post") {
permissions = {
view_limited: false,
post_limited: true
post_limited: true,
};
} else if (perms === "private_view") {
permissions = {
view_limited: true,
post_limited: true
post_limited: true,
};
// generate key
/* wip
@ -101,45 +110,50 @@ async function createThread(e) {
}
*/
}
window.emit("create_thread", {
window.emit(
"create_thread",
{
name: name.value,
permissions,
members
}, msg => {
members,
},
(msg) => {
chooseThread({
name: name.value,
id: msg.id
id: msg.id,
});
document.getElementById('createthread').remove();
// 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;
if (!msg) return;
window.emit("send_message", {
message: msg,
thread: window.currentThreadId
thread: window.currentThreadId,
});
document.getElementById("msg").value = "";
}
function newThread(e) {
let form = document.getElementById('createthread');
function newThread() {
let form = document.getElementById("createthread");
if (form) {
form.remove();
e.target.textContent = 'create';
}
else {
window.threadmembers = [ window.name ];
document.getElementById('threads').insertAdjacentElement('afterend', html.node`
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">thread name</label>
<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 />
@ -153,7 +167,7 @@ function newThread(e) {
/>
<label for="private_view">only members can view and post</label
><br /><br />
<span>members</span><br />
<label class="heading" for="membername">members</label>
<input type="text" id="membername" placeholder="username" onkeydown=${(e) => {
if (e.key == "Enter") {
e.preventDefault();
@ -165,65 +179,68 @@ function newThread(e) {
<p class='member'>${window.name}</p>
</div>
<br />
<input id="submitthread" type="submit" value="create" />
<button id="submitthread" type="submit">create</button>
</form>
`);
e.target.textContent = 'cancel';
`
);
document.getElementById("newthread").textContent = "cancel";
}
}
function switchTab(event){
for (let tab of document.querySelectorAll('.tab'))
tab.classList.remove('active');
for (let tab of document.querySelectorAll('.tabcontent'))
tab.classList.add('hidden');
event.target.classList.add('active');
document.getElementById(event.target.id.substring(0, event.target.id.length - 3))
.classList.remove('hidden');
function switchTab(event) {
for (let tab of document.querySelectorAll(".tab"))
tab.classList.remove("active");
for (let tab of document.querySelectorAll(".tabcontent"))
tab.classList.add("hidden");
event.target.classList.add("active");
document
.getElementById(event.target.id.substring(0, event.target.id.length - 3))
.classList.remove("hidden");
}
render(document.body, html`
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>
<button id="newthread" onclick=${newThread}>create</button>
</div>
<div id="thread" class="column">
<div id='title'>
thread: <strong id="threadname">meow</strong>
</div>
<button id='messagetab' class='tab active' onclick=${switchTab}>messages</button>
<button id='spacetab' class='tab' onclick=${switchTab}>space</button>
<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button>
<div id='message' class='tabcontent'>
<div id='messages'>
</div>
<div id="title">thread: <strong id="threadname">meow</strong></div>
<button id="messagetab" class="tab active" onclick=${switchTab}>
messages
</button>
<button id="spacetab" class="tab" onclick=${switchTab}>space</button>
<button id="loadmore" class="hidden" onclick=${loadMessages}>
load more messages
</button>
<div id="message" class="tabcontent">
<div id="messages"></div>
<form id="msginput" onsubmit=${sendMessage}>
<input type="text" placeholder="write a message..." id="msg" />
<button type="submit" class="hidden" id="sendmsg"></button>
</form>
</div>
<div id='space' class='tabcontent'></div>
<div id="space" class="tabcontent"></div>
</div>
`);
`
);
window.socket.on("new_message", (msg) => {
if (msg.thread !== window.currentThreadId)
return;
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;
if (!window.earliestMessage) window.earliestMessage = msg.id;
});
window.socket.on("new_thread", addThread);
window.socket.on("new_thread", (thread) => addThread(thread, true));
window.emit("list_threads", {}, msg => {
window.emit("list_threads", {}, (msg) => {
document.getElementById("threadlist").innerHTML = "";
for (let thread of msg.threads)
addThread(thread);
for (let thread of msg.threads) addThread(thread);
chooseThread(msg.threads[0]);
});

View File

@ -1,4 +1,4 @@
import { render, html } from '/uhtml.js';
import { render, html } from "/uhtml.js";
function rand() {
let str = "";
@ -16,19 +16,26 @@ async function auth() {
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
signingKeys: window.keys.priv,
});
window.socket.emit("authenticate", { name: window.name, message: sig },
msg => {
window.socket.emit(
"authenticate",
{ name: window.name, message: sig },
(msg) => {
if (!msg.success) {
console.log('authenticate failed');
console.log("authenticate failed", msg);
return;
}
if (document.getElementById("register")) {
document.getElementById("register").remove();
import('/app.js');
});
import("/app.js");
}
}
);
}
render(document.body, html`
<div id="register" class='hidden'>
render(
document.body,
html`
<div id="register" class="hidden">
<h1>welcome to vybe</h1>
<h3>a communication network (beta)</h3>
<p>
@ -36,20 +43,26 @@ render(document.body, html`
for security, rather than passwords. your keys are stored in your
browser storage only, so do this on a browser you can access again.
</p>
<form onsubmit=${async e => {
<form
onsubmit=${async (e) => {
e.preventDefault();
const name = document.getElementById("name").value;
if (!name)
return;
if (!name) return;
const keys = await openpgp.generateKey({
userIDs: [{ name }],
});
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.emit("create_user", { name, pubkey: keys.publicKey }, msg => {
window.emit(
"create_user",
{ name, pubkey: keys.publicKey },
(msg) => {
if (!msg.success) {
document.querySelector('#registerform').insertAdjacentHTML('afterend', `
<p>${msg.message}</p>`);
document.querySelector("#registerform").insertAdjacentHTML(
"afterend",
`
<p>${msg.message}</p>`
);
return;
}
window.keys = { priv, pub };
@ -57,23 +70,37 @@ render(document.body, html`
localStorage.setItem("name", name);
window.name = name;
auth();
});
}} id="registerform">
}
);
}}
id="registerform"
>
<label for="name">username: </label>
<input id="name" type="text" />
<input id="submit" type="submit" value='generate keys & register'>
<input id="submit" type="submit" value="generate keys & register" />
</form>
</div>
`);
`
);
const gensession = async () => {
window.session = rand();
window.emit = (type, data, callback) =>
window.socket.emit(
type,
{
...data,
__session: window.session,
},
callback
);
};
window.onload = async () => {
window.socket = io();
window.session = rand();
window.emit = (type, data, callback) => window.socket.emit(type, {
...data,
__session: window.session,
}, callback);
await gensession();
let keys = localStorage.getItem("keys");
if (keys) {
@ -81,10 +108,13 @@ window.onload = async () => {
keys = JSON.parse(keys);
window.keys = {
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
pub: await openpgp.readKey({ armoredKey: keys.publicKey })
pub: await openpgp.readKey({ armoredKey: keys.publicKey }),
};
await auth();
}
else
document.getElementById('register').classList.remove('hidden');
} else document.getElementById("register").classList.remove("hidden");
window.socket.io.on("reconnect", async (attempt) => {
await gensession();
if (localStorage.getItem("keys")) await auth();
});
};

View File

@ -18,20 +18,47 @@
body,
button,
input {
background: #020202;
color: #eaeaea;
border: none;
outline: none;
}
body {
background: #020202;
display: flex;
align-items: stretch;
margin: 0;
min-width: min-content;
}
h3, h4 {
margin: 10px 0;
button,
input,
.tab {
padding: 5px 7px;
}
button {
border-color: #767676;
background: #4f4f4f;
}
input {
background: #2f2f2f;
border-bottom: 2px solid transparent;
padding-bottom: 3px;
}
input:focus {
border-bottom: 2px solid #4f4f4f;
}
input::placeholder {
color: #aaa;
}
button:hover,
.tab:hover {
background-color: #3b3b3b;
}
label.heading {
margin-bottom: 5px;
display: block;
}
h3,
h4 {
margin: 10px 0;
}
.hidden {
display: none;
@ -44,11 +71,10 @@
#threads {
max-width: 250px;
}
.thread.selected {
background-color: #4f4f4f;
}
.thread:hover {
background-color: #3b3b3b;
.thread {
display: block;
width: 100%;
text-align: left;
}
#newthread {
margin-top: 5px;
@ -63,9 +89,9 @@
margin: 4px 2px;
}
.tab {
background: transparent;
border: 0;
margin-top: 2px;
padding: 5px 7px;
color: #ccc;
font-weight: 500;
}
@ -73,9 +99,6 @@
background-color: #4f4f4f;
color: #fff;
}
.tab:hover {
background-color:#3b3b3b;
}
#messages {
margin: 4px 2px;
}
@ -97,6 +120,5 @@
}
</style>
</head>
<body>
</body>
<body></body>
</html>

View File

@ -3,15 +3,17 @@ const http = require("http");
const { Server } = require("socket.io");
const compression = require("compression");
const events = Object.fromEntries([
'create_user',
'get_history',
'send_message',
'authenticate',
'create_thread',
'list_threads',
'get_keys',
].map(event => [event, require('./src/' + event)]));
const events = Object.fromEntries(
[
"create_user",
"get_history",
"send_message",
"authenticate",
"create_thread",
"list_threads",
"get_keys",
].map((event) => [event, require("./src/" + event)])
);
const app = express();
app.use(compression());
@ -32,11 +34,10 @@ io.on("connection", (socket) => {
events[event](msg, callback, socket, io)
);
}
socket.on('disconnect', reason => {
socket.on("disconnect", (reason) => {
let sockets = io.cache[socket.username];
if (sockets)
sockets.splice(sockets.indexOf(socket.id), 1);
})
if (sockets) sockets.splice(sockets.indexOf(socket.id), 1);
});
});
server.listen(PORT, () => {

View File

@ -3,7 +3,7 @@ const authwrap = require("./authwrap");
const create_thread = async (msg, respond, socket, io) => {
// validate inputs
if (typeof msg.name !== 'string') {
if (typeof msg.name !== "string") {
return respond({
success: false,
message: "thread name required",
@ -77,24 +77,23 @@ const create_thread = async (msg, respond, socket, io) => {
permissions: {
is_member: false,
view: true,
post: !msg.permissions || !msg.permissions.post_limited
post: !msg.permissions || !msg.permissions.post_limited,
},
});
}
}
}
else {
} else {
for (let member of msg.members) {
for (let socket of io.cache[member.user]) {
for (let socket of io.cache[member.name]) {
io.to(socket).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: {
is_member: true,
view: true,
post: true
post: true,
},
key: member.key
key: member.key,
});
}
}