add and remove thread members
parent
043a68874b
commit
c87c193118
|
@ -69,7 +69,8 @@ window.getUser = async (id, name) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.makeUser = (user, url) => {
|
window.makeUser = (user, url, removable) => {
|
||||||
|
removable &&= window.currentThread.permissions.admin && !user.permissions?.admin;
|
||||||
let span = html.node`
|
let span = html.node`
|
||||||
<span class='user' onclick=${function(e) {
|
<span class='user' onclick=${function(e) {
|
||||||
let member = document.getElementById('member');
|
let member = document.getElementById('member');
|
||||||
|
@ -80,6 +81,10 @@ window.makeUser = (user, url) => {
|
||||||
member.user = user;
|
member.user = user;
|
||||||
document.getElementById('membername').textContent = user.displayname;
|
document.getElementById('membername').textContent = user.displayname;
|
||||||
document.getElementById('memberusername').textContent = `${user.name}@${url}`;
|
document.getElementById('memberusername').textContent = `${user.name}@${url}`;
|
||||||
|
if (removable)
|
||||||
|
document.getElementById('removemember').classList.remove('hidden');
|
||||||
|
else
|
||||||
|
document.getElementById('removemember').classList.add('hidden');
|
||||||
}}>${user.displayname}</span>`;
|
}}>${user.displayname}</span>`;
|
||||||
span.user = user;
|
span.user = user;
|
||||||
return span;
|
return span;
|
||||||
|
@ -224,12 +229,14 @@ document.body.append(html.node`
|
||||||
<div id='userlist'>
|
<div id='userlist'>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id='disconnect' onclick=${function(event) {
|
<p>
|
||||||
document.getElementById('instance' + this.parentElement.instance.id).remove();
|
<button id='disconnect' onclick=${function(event) {
|
||||||
window.instancelist.splice(window.instancelist.indexOf(this.parentElement.instance), 1);
|
document.getElementById('instance' + this.parentElement.instance.id).remove();
|
||||||
saveInstances();
|
window.instancelist.splice(window.instancelist.indexOf(this.parentElement.instance), 1);
|
||||||
document.getElementById('instance').classList.add('hidden');
|
saveInstances();
|
||||||
}}>disconnect</button>
|
document.getElementById('instance').classList.add('hidden');
|
||||||
|
}}>disconnect</button>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr class='separator' color='#505050'>
|
<hr class='separator' color='#505050'>
|
||||||
<!-- create thread column goes here -->
|
<!-- create thread column goes here -->
|
||||||
|
@ -237,13 +244,32 @@ document.body.append(html.node`
|
||||||
<div id='thread' class='column hidden'></div>
|
<div id='thread' class='column hidden'></div>
|
||||||
<hr class='separator' color='#505050'>
|
<hr class='separator' color='#505050'>
|
||||||
<div id='member' class='column hidden'>
|
<div id='member' class='column hidden'>
|
||||||
|
<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>
|
||||||
<p>
|
<p>
|
||||||
<button onclick=${e =>
|
<button id='removemember' onclick=${e => {
|
||||||
document.getElementById('member').classList.add('hidden')
|
let member = document.getElementById('member');
|
||||||
}>close</button>
|
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>
|
||||||
</p>
|
</p>
|
||||||
<p>user: <strong id='membername'></strong></p>
|
|
||||||
<p id='memberusername'></p>
|
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#instances {
|
#instances, #membershead {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
@ -180,15 +180,12 @@
|
||||||
#profile {
|
#profile {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
}
|
}
|
||||||
#instance {
|
#instance, #member {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
}
|
}
|
||||||
#disconnect {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
.thread {
|
.thread {
|
||||||
padding: 2px 3px;
|
padding: 2px 3px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
|
|
@ -53,27 +53,29 @@ function loadMessages(callback) {
|
||||||
earliestMessage = msg.messages[msg.messages.length - 1].id;
|
earliestMessage = msg.messages[msg.messages.length - 1].id;
|
||||||
let users = {};
|
let users = {};
|
||||||
for (let message of msg.messages) {
|
for (let message of msg.messages) {
|
||||||
let span;
|
let user;
|
||||||
if (message.name)
|
if (message.user.name)
|
||||||
span = window.makeUser({
|
user = message.user;
|
||||||
displayname: message.displayname,
|
|
||||||
name: message.name,
|
|
||||||
id: message.user
|
|
||||||
}, instance.url);
|
|
||||||
else {
|
else {
|
||||||
try {
|
user = users[message.user.id];
|
||||||
span = window.makeUser(users[message.userid] || (
|
if (user === undefined)
|
||||||
users[message.userid] = await window.getUser(message.userid)
|
try {
|
||||||
), message.userid.split('@')[1]);
|
user = users[message.user.id] = await window.getUser(message.user.id);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.log(`error getting user ${message.userid}`, e);
|
console.log(`error getting user ${message.user.id}:`, e);
|
||||||
span = html.node`<span>${message.userid}</span>`;
|
users[message.user.id] = false;
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
user.id = message.user.id;
|
||||||
|
user.permissions = message.user.permissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message = html.node`<div class='message'>: ${message.content}</div>`;
|
let div = html.node`<div class='message'>: ${message.content}</div>`;
|
||||||
message.prepend(span);
|
div.prepend(user ? window.makeUser(user,
|
||||||
messages.prepend(message);
|
user.id.split?.('@')[1] || instance.url, true)
|
||||||
|
: html.node`<span>${message.user.id}</span>`);
|
||||||
|
messages.prepend(div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg.more)
|
if (msg.more)
|
||||||
|
@ -90,11 +92,8 @@ function loadMessages(callback) {
|
||||||
const messages = document.getElementById('messages');
|
const messages = document.getElementById('messages');
|
||||||
let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight;
|
let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight;
|
||||||
let div = html.node`<div class='message'>: ${message.content}</div>`;
|
let div = html.node`<div class='message'>: ${message.content}</div>`;
|
||||||
div.prepend(window.makeUser({
|
div.prepend(window.makeUser(message.user,
|
||||||
name: window.name,
|
message.user.id.split?.('@')[1] || instance.url, true));
|
||||||
displayname: window.displayname,
|
|
||||||
id: window.id
|
|
||||||
}, location.host));
|
|
||||||
messages.append(div);
|
messages.append(div);
|
||||||
if (scroll)
|
if (scroll)
|
||||||
messages.scroll(0, messages.scrollHeight - messages.clientHeight);
|
messages.scroll(0, messages.scrollHeight - messages.clientHeight);
|
||||||
|
|
|
@ -15,6 +15,8 @@ function setVisibility() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseThread() {
|
function chooseThread() {
|
||||||
|
if (!document.getElementById('removemember').classList.contains('hidden'))
|
||||||
|
document.getElementById('member').classList.add('hidden');
|
||||||
const edit = document.getElementById('edit');
|
const edit = document.getElementById('edit');
|
||||||
let thread = this.thread;
|
let thread = this.thread;
|
||||||
let url = new URL(location);
|
let url = new URL(location);
|
||||||
|
@ -46,7 +48,7 @@ function chooseThread() {
|
||||||
window.currentThread = thread;
|
window.currentThread = thread;
|
||||||
window.currentInstance = thread.instance;
|
window.currentInstance = thread.instance;
|
||||||
let tab = url.searchParams.get('tab');
|
let tab = url.searchParams.get('tab');
|
||||||
if (['message', 'space', 'stream'].indexOf(tab) === -1)
|
if (!['message', 'space', 'stream'].includes(tab))
|
||||||
tab = null;
|
tab = null;
|
||||||
if (tab || this.tab) {
|
if (tab || this.tab) {
|
||||||
if (tab)
|
if (tab)
|
||||||
|
@ -68,8 +70,19 @@ function chooseThread() {
|
||||||
document.getElementById('memberlist').replaceChildren(
|
document.getElementById('memberlist').replaceChildren(
|
||||||
...await Promise.all(msg.thread.members.map(async member => {
|
...await Promise.all(msg.thread.members.map(async member => {
|
||||||
let p = document.createElement('p');
|
let p = document.createElement('p');
|
||||||
p.append(member.name ? window.makeUser(member, window.currentInstance.url)
|
if (member.name)
|
||||||
: makeUser(await window.getUser(member.id), member.id.split('@')[1]));
|
p.append(window.makeUser(member, window.currentInstance.url, true));
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
let user = await window.getUser(member.id);
|
||||||
|
user.id = member.id;
|
||||||
|
user.permissions = member.permissions;
|
||||||
|
p.append(window.makeUser(user, user.id.split?.('@')[1], true));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(`error getting user ${member.id}: ${e}`);
|
||||||
|
p.append(html.node`<span>${member.id}</span>`);
|
||||||
|
}
|
||||||
return p;
|
return p;
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -137,7 +150,7 @@ function newThread() {
|
||||||
return;
|
return;
|
||||||
let at = name.value.split('@');
|
let at = name.value.split('@');
|
||||||
let url = at[1] || instancediv.instance.url;
|
let url = at[1] || instancediv.instance.url;
|
||||||
let error = document.getElementById('membererror');
|
let error = document.getElementById('newmembererror');
|
||||||
let user = await window.getUser('@' + url, at[0]);
|
let user = await window.getUser('@' + url, at[0]);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
error.innerText = 'user not found';
|
error.innerText = 'user not found';
|
||||||
|
@ -213,8 +226,8 @@ function newThread() {
|
||||||
addMember();
|
addMember();
|
||||||
}
|
}
|
||||||
}} />
|
}} />
|
||||||
<button id='addmember' type='button' onclick=${addMember}>add</button>
|
<button type='button' onclick=${addMember}>add</button>
|
||||||
<p id='membererror'></p>
|
<p id='newmembererror'></p>
|
||||||
<div id='newmembers'>
|
<div id='newmembers'>
|
||||||
<p class='member'>${members[0].name}</p>
|
<p class='member'>${members[0].name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -306,6 +319,51 @@ function clickedTab(event) {
|
||||||
window.history.pushState(null, '', url.toString());
|
window.history.pushState(null, '', url.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addMember() {
|
||||||
|
let name = document.getElementById('addmembername');
|
||||||
|
let error = document.getElementById('membererror');
|
||||||
|
function close() {
|
||||||
|
error.innerText = '';
|
||||||
|
name.value = '';
|
||||||
|
document.getElementById('addmember').classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (!name.value) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at = name.value.split('@');
|
||||||
|
let url = at[1] || window.currentInstance.url;
|
||||||
|
let user = await window.getUser('@' + url, at[0]);
|
||||||
|
if (!user) {
|
||||||
|
error.innerText = 'user not found';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
error.innerText = '';
|
||||||
|
if (window.currentInstance.url !== url) {
|
||||||
|
user.id += '@' + url;
|
||||||
|
user.name += '@' + url;
|
||||||
|
}
|
||||||
|
for (let p of document.getElementById('memberlist').children)
|
||||||
|
if (p.children[0].user.id == user.id) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.currentInstance.emit('add_member', {
|
||||||
|
id: String(user.id),
|
||||||
|
thread: window.currentThread.id
|
||||||
|
}, msg => {
|
||||||
|
if (!msg.success) {
|
||||||
|
console.log('add_member failed:', msg.message);
|
||||||
|
error.textContent = 'error: ' + msg.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
let p = document.createElement('p');
|
||||||
|
p.append(window.makeUser(user, url, true));
|
||||||
|
document.getElementById('memberlist').append(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function loadThreads(instancediv, select) {
|
async function loadThreads(instancediv, select) {
|
||||||
function makeThread(thread) {
|
function makeThread(thread) {
|
||||||
thread.instance = instancediv.instance;
|
thread.instance = instancediv.instance;
|
||||||
|
@ -344,7 +402,20 @@ async function loadThreads(instancediv, select) {
|
||||||
<hr class='separator' color='#505050'>
|
<hr class='separator' color='#505050'>
|
||||||
<div id='members' class='column hidden'>
|
<div id='members' class='column hidden'>
|
||||||
<p id='visibility'></p>
|
<p id='visibility'></p>
|
||||||
<h4>members</h4>
|
<div id='membershead'>
|
||||||
|
<strong>members</strong>
|
||||||
|
<button onclick=${e => {
|
||||||
|
document.getElementById('addmember').classList.remove('hidden');
|
||||||
|
document.getElementById('addmembername').focus();
|
||||||
|
}}>add</button>
|
||||||
|
</div>
|
||||||
|
<div id='addmember' class='hidden'>
|
||||||
|
<input id='addmembername' onblur=${addMember} onkeydown=${event => {
|
||||||
|
if (event.key === 'Enter')
|
||||||
|
addMember();
|
||||||
|
}}>
|
||||||
|
<p id='membererror'></p>
|
||||||
|
</div>
|
||||||
<div id='memberlist'>
|
<div id='memberlist'>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,17 +25,26 @@ async function send_message(msg, respond) {
|
||||||
await db.query('select user from member where member.thread = ?', msg.thread)
|
await db.query('select user from member where member.thread = ?', msg.thread)
|
||||||
).rows.map(i => i.user);
|
).rows.map(i => i.user);
|
||||||
// get perms
|
// get perms
|
||||||
const permissions = await db.query(
|
const perms = (await db.query(
|
||||||
`select * from permission
|
`select * from permission where thread = ?`,
|
||||||
where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'`,
|
msg.thread)).rows;
|
||||||
msg.thread);
|
let userperms = {};
|
||||||
|
for (let p of perms)
|
||||||
|
if (p.type === 'user' && p.user == msg.auth_user.id)
|
||||||
|
userperms[p.permission] = ['admin', 'post', 'view'].includes(p.permission)
|
||||||
|
? p.value === 'true' : p.value;
|
||||||
for (let id in vybe.users) {
|
for (let id in vybe.users) {
|
||||||
if (permissions.rows.length > 0 || members.includes(id)) {
|
if (perms.find(p => p.type === 'everyone' && p.permission === 'view' && p.value === 'true')
|
||||||
|
|| members.includes(id)) {
|
||||||
for (let s of vybe.users[id].sockets) {
|
for (let s of vybe.users[id].sockets) {
|
||||||
s.emit('new_message', {
|
s.emit('new_message', {
|
||||||
id,
|
id,
|
||||||
userid: msg.auth_user.id,
|
user: {
|
||||||
name: msg.auth_user.displayname,
|
id: msg.auth_user.id,
|
||||||
|
name: msg.auth_user.name,
|
||||||
|
displayname: msg.auth_user.displayname,
|
||||||
|
permissions: userperms
|
||||||
|
},
|
||||||
content: msg.message,
|
content: msg.message,
|
||||||
thread: msg.thread
|
thread: msg.thread
|
||||||
});
|
});
|
||||||
|
@ -67,8 +76,8 @@ async function get_history(msg, respond) {
|
||||||
message: "you can't view this thread"
|
message: "you can't view this thread"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const messages = (await db.query(
|
const messages = (await db.query(`
|
||||||
`select coalesce(displayname, name) as displayname, name, user as userid, post.id, content
|
select coalesce(displayname, name) as displayname, name, user as userid, post.id, content
|
||||||
from post
|
from post
|
||||||
left join user on post.user = user.id
|
left join user on post.user = user.id
|
||||||
where ${msg.before ? 'post.id < ? and' : ''}
|
where ${msg.before ? 'post.id < ? and' : ''}
|
||||||
|
@ -77,9 +86,25 @@ async function get_history(msg, respond) {
|
||||||
limit 101`,
|
limit 101`,
|
||||||
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
||||||
)).rows;
|
)).rows;
|
||||||
|
let perms = {};
|
||||||
|
for (let p of (await db.query(
|
||||||
|
`select * from permission where type = 'user' and thread = ?`,
|
||||||
|
msg.thread)).rows)
|
||||||
|
(perms[p.user] || (perms[p.user] = {}))[p.permission] =
|
||||||
|
['admin', 'post', 'view'].includes(p.permission)
|
||||||
|
? p.value === 'true' : p.value;
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
messages: messages.slice(0, 100),
|
messages: messages.slice(0, 100).map(message => ({
|
||||||
|
id: message.id,
|
||||||
|
user: {
|
||||||
|
id: message.userid,
|
||||||
|
name: message.name,
|
||||||
|
displayname: message.displayname,
|
||||||
|
permissions: perms[message.userid]
|
||||||
|
},
|
||||||
|
content: message.content
|
||||||
|
})),
|
||||||
more: messages.length > 100
|
more: messages.length > 100
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,9 +373,83 @@ async function edit_thread(msg, respond) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function add_member(msg, respond) {
|
||||||
|
if (!msg.thread || !msg.id) {
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: 'invalid msg'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!(await check_permission(msg.auth_user.id, msg.thread)).admin)
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: "user doesn't have permission"
|
||||||
|
});
|
||||||
|
if ((await db.query(
|
||||||
|
`select user from thread
|
||||||
|
join member on thread.id = member.thread
|
||||||
|
where thread.id = ? and user = ?`,
|
||||||
|
[msg.thread, msg.id]
|
||||||
|
)).rows.length)
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: 'user is already a member of this thread'
|
||||||
|
});
|
||||||
|
await db.query(`
|
||||||
|
insert into member (thread, user) values (?, ?)`,
|
||||||
|
[msg.thread, msg.id]);
|
||||||
|
return respond({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove_member(msg, respond) {
|
||||||
|
if (!msg.thread || !msg.id) {
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: 'invalid msg'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!(await check_permission(msg.auth_user.id, msg.thread)).admin)
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: "user doesn't have permission"
|
||||||
|
});
|
||||||
|
let members = (await db.query(
|
||||||
|
`select member.user, p.value from thread
|
||||||
|
join member on thread.id = member.thread
|
||||||
|
left join permission p on p.user = member.user
|
||||||
|
and p.type = 'user' and p.permission = 'admin' and p.value = 'true'
|
||||||
|
where thread.id = ?`,
|
||||||
|
msg.thread
|
||||||
|
)).rows;
|
||||||
|
if (!members.find(member => member.user == msg.id))
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: "user isn't a member of this thread"
|
||||||
|
});
|
||||||
|
members = members.filter(member => member.value);
|
||||||
|
if (members.length <= 1 && members[0]?.user == msg.id)
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: "can't remove the only admin of the thread"
|
||||||
|
});
|
||||||
|
await db.query(`
|
||||||
|
delete from member where thread = ? and user = ?`,
|
||||||
|
[msg.thread, msg.id]);
|
||||||
|
await db.query(`
|
||||||
|
delete from permission where type = 'user' and thread = ? and user = ?`,
|
||||||
|
[msg.thread, msg.id]);
|
||||||
|
return respond({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
create_thread: authwrap(create_thread),
|
create_thread: authwrap(create_thread),
|
||||||
list_threads: authwrap(list_threads),
|
list_threads: authwrap(list_threads),
|
||||||
get_thread: authwrap(get_thread),
|
get_thread: authwrap(get_thread),
|
||||||
edit_thread: authwrap(edit_thread)
|
edit_thread: authwrap(edit_thread),
|
||||||
|
add_member: authwrap(add_member),
|
||||||
|
remove_member: authwrap(remove_member)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue