space ui and updates

main
jerl 2024-03-18 23:07:48 -07:00
parent f347e659e9
commit 26530690b9
15 changed files with 555 additions and 427 deletions

View File

@ -122,6 +122,11 @@ async function createThread(e) {
chooseThread({
name: name.value,
id: msg.id,
permissions: {
is_member: true,
view: true,
post: true
}
});
// since the form exists, this will perform cleanup
newThread();
@ -228,7 +233,7 @@ render(document.body, html`
</div>
`);
window.socket.on("new_message", (msg) => {
window.socket.on("new_message", msg => {
if (msg.thread !== window.currentThreadId)
return;
document.getElementById("messages").appendChild(html.node`
@ -247,3 +252,5 @@ window.emit("list_threads", {}, msg => {
addThread(thread);
chooseThread(msg.threads[0]);
});
import('/space.js');

View File

@ -20,21 +20,21 @@ async function auth() {
"authenticate",
{ name: window.name, message: sig },
msg => {
let register = document.getElementById('register');
if (!msg.success) {
console.log('authenticate failed', msg);
register.classList.remove('hidden');
return;
}
if (document.getElementById("register")) {
document.getElementById("register").remove();
if (register) {
register.remove();
import('/app.js');
}
}
);
}
render(
document.body,
html`
render(document.body, html`
<div id="register" class='hidden'>
<h1>welcome to vybe</h1>
<h3>a communication network (beta)</h3>
@ -43,8 +43,7 @@ render(
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)
@ -61,8 +60,7 @@ render(
if (!msg.success) {
document.querySelector('#registerform').insertAdjacentHTML(
'afterend',
`
<p>${msg.message}</p>`
`<p>${msg.message}</p>`
);
return;
}
@ -73,16 +71,13 @@ render(
auth();
}
);
}}
id="registerform"
>
}} id="registerform">
<label for="name">username: </label>
<input id="name" type="text" />
<input id="submit" type="submit" value='generate keys & register' />
<button id="submit" type="submit">generate keys & register</button>
</form>
</div>
`
);
`);
const gensession = async () => {
window.session = rand();

View File

@ -14,6 +14,16 @@
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
scrollbar-color: #505050 #111;
}
::-webkit-scrollbar-thumb {
background: #505050;
}
::-webkit-scrollbar {
background-color: #111;
}
::-webkit-scrollbar-corner {
background-color: #111;
}
body,
button,
@ -33,6 +43,9 @@
margin: 0;
min-width: min-content;
}
#register {
margin-inline: 14px;
}
button,
input,
.tab {
@ -42,9 +55,9 @@
background: #303030;
}
input {
background: #171717;
border-bottom: 2px solid transparent;
background: #1b1b1b;
padding-bottom: 3px;
border-bottom: 2px solid transparent;
}
input:focus {
border-bottom: 2px solid #4f4f4f;
@ -75,16 +88,14 @@
}
.column {
flex: 1;
margin: 5px;
overflow: hidden;
margin: 5px;
}
#threads {
max-width: 250px;
}
.thread {
display: block;
width: 100%;
text-align: left;
padding: 2px;
}
#newthread {
margin-top: 5px;
@ -98,30 +109,31 @@
#thread {
display: flex;
flex-direction: column;
margin: 0;
}
#title {
margin: 4px 2px;
margin: 4px;
}
#tabs {
margin-block: 2px;
margin: 4px 2px;
}
.tab {
padding: 5px 7px;
background-color: #1f1f1f;
border: 0;
margin-top: 2px;
color: #ddd;
font-weight: 500;
}
.tabcontent {
flex-grow: 1;
margin: 2px;
}
#message {
display: flex;
flex-direction: column;
}
#messages {
margin: 4px 2px;
margin: 2px;
flex-grow: 1;
}
#msginput {
@ -149,6 +161,14 @@
.member {
margin: 5px 0;
}
#space {
margin: 0;
position: relative;
overflow: auto;
}
.span {
position: absolute;
}
</style>
</head>
<body></body>

93
client/space.js Normal file
View File

@ -0,0 +1,93 @@
let space = document.getElementById('space');
let scale = 1;
let editing;
let dragging;
let moved;
let offset;
function mousemove(event) {
let left = (event.clientX - space.offsetLeft) * scale - offset.x;
let top = (event.clientY - space.offsetTop) * scale - offset.y;
dragging.style.left = `${left < 0 ? 0 : left}px`;
dragging.style.top = `${top < 0 ? 0 : top}px`;
moved = true;
//save
}
function remove(span) {
//remove
span.remove();
}
function add(x, y) {
let span = document.createElement('span');
span.classList.add('span');
span.contentEditable = true;
span.spellcheck = false;
span.scale = 1;
span.style.left = `${x}px`;
span.style.top = `${y}px`;
span.style.transform = 'translate(-50%, -50%)';
span.onkeydown = function(event) {
if (event.key === 'Enter' && !event.getModifierState('Shift')) {
event.preventDefault();
editing = null;
span.blur();
}
};
span.oninput = function(event) {
this.time = Date.now();
//save
};
span.onblur = function(event) {
if (!this.innerText)
remove(this);
};
span.onwheel = function(event) {
event.preventDefault();
this.scale *= 1 - event.deltaY * .001;
this.style.transform = `translate(-50%, -50%) scale(${this.scale})`;
//save
};
span.onmousedown = function(event) {
if (dragging || editing === this)
return;
dragging = this;
event.preventDefault();
offset = {
x: event.clientX - (space.offsetLeft + this.offsetLeft),
y: event.clientY - (space.offsetTop + this.offsetTop)
};
moved = false;
document.addEventListener('mousemove', mousemove);
};
span.onmouseup = function(event) {
event.stopPropagation();
document.removeEventListener('mousemove', mousemove);
dragging = null;
if (moved)
return;
this.focus();
editing = this;
};
space.append(span);
return span;
}
space.onmouseup = event => {
if (dragging) {
dragging.onmouseup(event);
return;
}
if (editing) {
if (!editing.innerText)
remove(editing);
if (event.target !== editing)
editing = null;
return;
}
editing = add(event.offsetX + space.scrollLeft, event.offsetY + space.scrollTop);
editing.focus();
};

View File

@ -1,60 +1,73 @@
CREATE TABLE users (
create table user (
id integer primary key asc,
name text,
pubkey text,
created timestamp default current_timestamp
);
CREATE TABLE authentications (
create table authentication (
user integer,
salt text,
created timestamp default current_timestamp,
foreign key(user) references users(id)
foreign key(user) references user(id)
);
CREATE TABLE threads (
create table thread (
id integer primary key asc,
name text,
creator integer,
created timestamp default current_timestamp,
foreign key(creator) references users(id)
foreign key(creator) references user(id)
);
CREATE TABLE permissions (
create table permission (
thread integer,
user integer,
type text,
mutable boolean,
permission text,
value text,
foreign key(user) references users(id),
foreign key(thread) references threads(id)
foreign key(user) references user(id),
foreign key(thread) references thread(id)
);
CREATE TABLE members (
create table member (
thread integer,
user integer,
key_delivery text,
created timestamp default current_timestamp,
foreign key(user) references users(id),
foreign key(thread) references threads(id)
foreign key(user) references user(id),
foreign key(thread) references thread(id)
);
CREATE TABLE posts (
create table post (
id integer primary key asc,
user integer,
thread integer,
content text,
encrypted bool,
created timestamp default current_timestamp,
foreign key(user) references users(id),
foreign key(thread) references threads(id)
foreign key(user) references user(id),
foreign key(thread) references thread(id)
);
INSERT INTO threads (name) values ("meow");
INSERT INTO permissions
create table span (
id integer primary key asc,
thread integer,
creator integer,
created timestamp default current_timestamp,
content text,
x integer,
y integer,
scale decimal,
foreign key(creator) references user(id),
foreign key(thread) references thread(id)
);
insert into thread (name) values ("meow");
insert into permission
(thread, type, permission, value) values
(1, "everyone", "view", "true");
INSERT INTO permissions
insert into permission
(thread, type, permission, value) values
(1, "everyone", "post", "true");

View File

@ -8,7 +8,7 @@ const authenticate = async (msg, respond, socket, io) => {
message: "Invalid message",
});
}
const result = await db.query("select * from users where name = ?", [
const result = await db.query("select * from user where name = ?", [
msg.name,
]);
if (result.rows.length === 0) {
@ -34,11 +34,11 @@ const authenticate = async (msg, respond, socket, io) => {
});
}
const auths = await db.query(
"select * from authentications where user = ? and salt = ?",
"select * from authentication where user = ? and salt = ?",
[result.rows[0].id, data[1]]
);
if (auths.rows.length === 0) {
await db.query("insert into authentications (user, salt) values (?, ?)", [
await db.query("insert into authentication (user, salt) values (?, ?)", [
result.rows[0].id,
data[1],
]);

View File

@ -10,9 +10,9 @@ const authwrap = (fn) => async (msg, respond, socket, io) => {
});
}
const result = await db.query(
`select users.* from users join authentications
on authentications.user = users.id
where authentications.salt = ?`,
`select user.* from user join authentication
on authentication.user = user.id
where authentication.salt = ?`,
[msg.__session]
);
if (result.rows.length === 0) {

View File

@ -17,38 +17,38 @@ const create_thread = async (msg, respond, socket, io) => {
}
// add to db
const insert = await db.query(
"insert into threads (name, creator) values (?, ?) returning id",
"insert into thread (name, creator) values (?, ?) returning id",
[msg.name, msg.auth_user.id]
);
const thread_id = insert.rows[0].id;
// set up permissions
if (!msg.permissions || !msg.permissions.view_limited) {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
`insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "view", "true"]
);
if (!msg.permissions || !msg.permissions.post_limited) {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
`insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "post", "true"]
);
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
`insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
}
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
`insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "view", "true"]
);
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
`insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
@ -57,13 +57,13 @@ const create_thread = async (msg, respond, socket, io) => {
for (let user of msg.members) {
if (!user) continue;
// get user id
const id = await db.query("select id from users where name = ?", [
const id = await db.query("select id from user where name = ?", [
user.name,
]);
if (id.rows.length > 0) {
const user_id = id.rows[0].id;
await db.query(
"insert into members (thread, user, key_delivery) values (?, ?, ?)",
"insert into member (thread, user, key_delivery) values (?, ?, ?)",
[thread_id, user_id, user.key]
);
}

View File

@ -10,7 +10,7 @@ const create_user = async (msg, respond) => {
});
}
// ensure username is not taken
const result = await db.query("select * from users where name = ?", [
const result = await db.query("select * from user where name = ?", [
msg.name,
]);
if (result.rows.length > 0) {
@ -38,7 +38,7 @@ const create_user = async (msg, respond) => {
}
// add to db
const insert = await db.query(
"insert into users (name, pubkey) values (?, ?) returning id",
"insert into user (name, pubkey) values (?, ?) returning id",
[msg.name, msg.pubkey]
);
// respond

View File

@ -1,6 +1,6 @@
const db = require("../db");
const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
const check_permission = require("./helpers/check_permission");
const get_history = async (msg, respond) => {
if (msg.before && isNaN(Number(msg.before))) {
@ -15,18 +15,18 @@ const get_history = async (msg, respond) => {
message: "thread ID required",
});
}
if (!(await check_permissions(msg.auth_user.id, msg.thread)).view) {
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
return respond({
success: false,
message: "you can't view this thread",
});
}
const messages = await db.query(
`select users.name, posts.id, content from posts
join users on posts.user = users.id
${msg.before ? "where posts.id < ? and" : "where"}
`select user.name, post.id, content from post
join user on post.user = user.id
${msg.before ? "where post.id < ? and" : "where"}
thread = ?
order by posts.created desc
order by post.created desc
limit 101`,
msg.before ? [msg.before, msg.thread] : [msg.thread]
);

View File

@ -16,7 +16,7 @@ const get_keys = async (msg, respond, socket, io) => {
});
}
const keys = await db.query(
`select name, pubkey from users where name in (${msg.names
`select name, pubkey from user where name in (${msg.names
.map((i) => "?")
.join(",")})`,
msg.names

View File

@ -0,0 +1,37 @@
const db = require("../../db");
const check_permission = async (user_id, thread_id) => {
// get all the permissions for the thread
const permissions = await db.query(
"select * from permission where thread = ?",
[thread_id]
);
// check if the user is a member
const is_member =
(
await db.query("select * from member where thread = ? and user = ?", [
thread_id,
user_id,
])
).rows.length > 0;
const get_permission = (permission) => {
const relevant = permissions.rows.filter(
(i) => i.permission === permission
);
for (let i of relevant) {
if (i.type === "everyone" && i.value === "true") {
return true;
}
if (i.type === "members" && i.value === "true" && is_member) {
return true;
}
}
};
return {
is_member,
view: get_permission("view"),
post: get_permission("post"),
};
};
module.exports = check_permission;

View File

@ -1,37 +0,0 @@
const db = require("../../db");
const check_permissions = async (user_id, thread_id) => {
// get all the permissions for the thread
const permissions = await db.query(
"select * from permissions where thread = ?",
[thread_id]
);
// check if the user is a member
const is_member =
(
await db.query("select * from members where thread = ? and user = ?", [
thread_id,
user_id,
])
).rows.length > 0;
const get_permission = (permission) => {
const relevant = permissions.rows.filter(
(i) => i.permission === permission
);
for (let i of relevant) {
if (i.type === "everyone" && i.value === "true") {
return true;
}
if (i.type === "members" && i.value === "true" && is_member) {
return true;
}
}
};
return {
is_member,
view: get_permission("view"),
post: get_permission("post"),
};
};
module.exports = check_permissions;

View File

@ -1,26 +1,26 @@
const db = require("../db");
const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
const check_permission = require("./helpers/check_permission");
const list_threads = async (msg, respond) => {
const threads = await db.query(
`select name, id, members.key_delivery as key from threads
join permissions on threads.id = permissions.thread
left join members on threads.id = members.thread
where permissions.permission = 'view'
and permissions.value = 'true'
and ((permissions.type = 'everyone') or
permissions.type = 'members' and members.user = ?)
group by threads.id
order by threads.created desc`,
`select name, id, member.key_delivery as key from thread
join permission on thread.id = permission.thread
left join member on thread.id = member.thread
where permission.permission = 'view'
and permission.value = 'true'
and ((permission.type = 'everyone') or
permission.type = 'members' and member.user = ?)
group by thread.id
order by thread.created desc`,
[msg.auth_user.id]
);
// respond
const rows = [];
for (let i of threads.rows) {
for (let thread of threads.rows) {
rows.push({
...i,
permissions: await check_permissions(msg.auth_user.id, i.id),
...thread,
permissions: await check_permission(msg.auth_user.id, thread.id),
});
}
return respond({

View File

@ -1,6 +1,6 @@
const db = require("../db");
const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
const check_permission = require("./helpers/check_permission");
const send_message = async (msg, respond, socket, io) => {
if (!msg.thread) {
@ -9,7 +9,7 @@ const send_message = async (msg, respond, socket, io) => {
message: "thread ID required",
});
}
if (!(await check_permissions(msg.auth_user.id, msg.thread)).post) {
if (!(await check_permission(msg.auth_user.id, msg.thread)).post) {
return respond({
success: false,
message: "you can't post to this thread",
@ -17,19 +17,19 @@ const send_message = async (msg, respond, socket, io) => {
}
// add message and send it to everyone
const id = await db.query(
"insert into posts (user, thread, content, encrypted) values (?, ?, ?, ?) returning id",
"insert into post (user, thread, content, encrypted) values (?, ?, ?, ?) returning id",
[msg.auth_user.id, msg.thread, msg.message, msg.encrypted]
);
// get thread members
const members = (
await db.query(
"select name from users join members on members.user = users.id where members.thread = ?",
"select name from user join member on member.user = user.id where member.thread = ?",
[msg.thread]
)
).rows.map((i) => i.name);
// get perms
const permissions = await db.query(
"select * from permissions where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
[msg.thread]
);
for (let username in io.cache) {