Bug fixes, blocking and various other changes

This commit is contained in:
MathMan05 2024-08-21 11:37:26 -05:00
parent 42a438f6dc
commit dbee2f3628
22 changed files with 679 additions and 370 deletions

View file

@ -58,14 +58,14 @@ class Channel {
this.contextmenu.addbutton("Delete channel", function () { this.contextmenu.addbutton("Delete channel", function () {
console.log(this); console.log(this);
this.deleteChannel(); this.deleteChannel();
}, null, _ => { console.log(_); return _.isAdmin(); }); }, null, function () { return this.isAdmin(); });
this.contextmenu.addbutton("Edit channel", function () { this.contextmenu.addbutton("Edit channel", function () {
this.editChannel(); this.editChannel();
}, null, _ => { return _.isAdmin(); }); }, null, function () { return this.isAdmin(); });
this.contextmenu.addbutton("Make invite", function () { this.contextmenu.addbutton("Make invite", function () {
this.createInvite(); this.createInvite();
}, null, (_) => { }, null, function () {
return _.hasPermission("CREATE_INSTANT_INVITE") && _.type !== 4; return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4;
}); });
/* /*
this.contextmenu.addbutton("Test button",function(){ this.contextmenu.addbutton("Test button",function(){
@ -154,7 +154,7 @@ class Channel {
} }
setUpInfiniteScroller() { setUpInfiniteScroller() {
this.infinite = new InfiniteScroller(async function (id, offset) { this.infinite = new InfiniteScroller(async function (id, offset) {
const snowflake = SnowFlake.getSnowFlakeFromID(id, Message); const snowflake = this.messages.get(id).snowflake;
if (offset === 1) { if (offset === 1) {
if (this.idToPrev.has(snowflake)) { if (this.idToPrev.has(snowflake)) {
return this.idToPrev.get(snowflake)?.id; return this.idToPrev.get(snowflake)?.id;
@ -184,12 +184,15 @@ class Channel {
const html = messgage.buildhtml(); const html = messgage.buildhtml();
return html; return html;
} }
else {
console.error(id + " not found");
}
} }
catch (e) { catch (e) {
console.error(e); console.error(e);
} }
}.bind(this), async function (id) { }.bind(this), async function (id) {
const message = SnowFlake.getSnowFlakeFromID(id, Message).getObject(); const message = this.messages.get(id);
try { try {
if (message) { if (message) {
message.deleteDiv(); message.deleteDiv();
@ -374,7 +377,7 @@ class Channel {
caps.classList.add("capsflex"); caps.classList.add("capsflex");
decdiv.classList.add("channeleffects"); decdiv.classList.add("channeleffects");
decdiv.classList.add("channel"); decdiv.classList.add("channel");
Channel.contextmenu.bind(decdiv, this); Channel.contextmenu.bindContextmenu(decdiv, this, undefined);
decdiv["all"] = this; decdiv["all"] = this;
for (const channel of this.children) { for (const channel of this.children) {
childrendiv.appendChild(channel.createguildHTML(admin)); childrendiv.appendChild(channel.createguildHTML(admin));
@ -400,7 +403,7 @@ class Channel {
if (this.hasunreads) { if (this.hasunreads) {
div.classList.add("cunread"); div.classList.add("cunread");
} }
Channel.contextmenu.bind(div, this); Channel.contextmenu.bindContextmenu(div, this, undefined);
if (admin) { if (admin) {
this.coatDropDiv(div); this.coatDropDiv(div);
} }
@ -815,12 +818,6 @@ class Channel {
async grabArround(id) { async grabArround(id) {
throw new Error("please don't call this, no one has implemented it :P"); throw new Error("please don't call this, no one has implemented it :P");
} }
buildmessage(message, next) {
const built = message.buildhtml(next);
if (built) {
document.getElementById("messages").prepend(built);
}
}
async buildmessages() { async buildmessages() {
/* /*
if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){ if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){

View file

@ -27,23 +27,23 @@ class Contextmenu {
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return {}; return {};
} }
makemenu(x, y, addinfo, obj) { makemenu(x, y, addinfo, other) {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("contextmenu", "flexttb"); div.classList.add("contextmenu", "flexttb");
for (const thing of this.buttons) { for (const thing of this.buttons) {
if (!thing[3](addinfo)) { if (!thing[3].bind(addinfo)(other)) {
continue; continue;
} }
const intext = document.createElement("button"); const intext = document.createElement("button");
intext.disabled = !thing[4](); intext.disabled = !thing[4].bind(addinfo)(other);
intext.classList.add("contextbutton"); intext.classList.add("contextbutton");
intext.textContent = thing[0]; intext.textContent = thing[0];
console.log(thing); console.log(thing);
if (thing[5] === "button") { if (thing[5] === "button") {
intext.onclick = thing[1].bind(addinfo, obj); intext.onclick = thing[1].bind(addinfo, other);
} }
else if (thing[5] === "submenu") { else if (thing[5] === "submenu") {
intext.onclick = thing[1].bind(addinfo); intext.onclick = thing[1].bind(addinfo, other);
} }
div.appendChild(intext); div.appendChild(intext);
} }
@ -58,11 +58,11 @@ class Contextmenu {
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
return this.div; return this.div;
} }
bind(obj, addinfo = undefined) { bindContextmenu(obj, addinfo, other) {
const func = (event) => { const func = (event) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, obj); this.makemenu(event.clientX, event.clientY, addinfo, other);
}; };
obj.addEventListener("contextmenu", func); obj.addEventListener("contextmenu", func);
return func; return func;

View file

@ -42,10 +42,14 @@ class Guild {
}); });
Guild.contextmenu.addbutton("Leave guild", function () { Guild.contextmenu.addbutton("Leave guild", function () {
this.confirmleave(); this.confirmleave();
}, null, function (_) { return _.properties.owner_id !== _.member.user.id; }); }, null, function (_) {
return this.properties.owner_id !== this.member.user.id;
});
Guild.contextmenu.addbutton("Delete guild", function () { Guild.contextmenu.addbutton("Delete guild", function () {
this.confirmDelete(); this.confirmDelete();
}, null, function (_) { return _.properties.owner_id === _.member.user.id; }); }, null, function (_) {
return this.properties.owner_id === this.member.user.id;
});
Guild.contextmenu.addbutton("Create invite", function () { Guild.contextmenu.addbutton("Create invite", function () {
console.log(this); console.log(this);
}, null, _ => true, _ => false); }, null, _ => true, _ => false);
@ -271,7 +275,7 @@ class Guild {
this.loadGuild(); this.loadGuild();
this.loadChannel(); this.loadChannel();
}; };
Guild.contextmenu.bind(img, this); Guild.contextmenu.bindContextmenu(img, this, undefined);
} }
else { else {
const div = document.createElement("div"); const div = document.createElement("div");
@ -283,7 +287,7 @@ class Guild {
this.loadGuild(); this.loadGuild();
this.loadChannel(); this.loadChannel();
}; };
Guild.contextmenu.bind(div, this); Guild.contextmenu.bindContextmenu(div, this, undefined);
} }
return divy; return divy;
} }

View file

@ -103,7 +103,7 @@ catch (e) {
thisuser = new Localuser(-1); thisuser = new Localuser(-1);
} }
{ {
const menu = new Contextmenu("create rightclick"); const menu = new Contextmenu("create rightclick"); //Really should go into the localuser class, but that's a later thing
menu.addbutton("Create channel", function () { menu.addbutton("Create channel", function () {
if (thisuser.lookingguild) { if (thisuser.lookingguild) {
thisuser.lookingguild.createchannels(); thisuser.lookingguild.createchannels();
@ -114,7 +114,7 @@ catch (e) {
thisuser.lookingguild.createcategory(); thisuser.lookingguild.createcategory();
} }
}, null, _ => { return thisuser.isAdmin(); }); }, null, _ => { return thisuser.isAdmin(); });
menu.bind(document.getElementById("channels")); menu.bindContextmenu(document.getElementById("channels"), 0, 0);
} }
const pasteimage = document.getElementById("pasteimage"); const pasteimage = document.getElementById("pasteimage");
let replyingto = null; let replyingto = null;

View file

@ -3,8 +3,9 @@ class InfiniteScroller {
getHTMLFromID; getHTMLFromID;
destroyFromID; destroyFromID;
reachesBottom; reachesBottom;
minDist = 3000; minDist = 2000;
maxDist = 8000; fillDist = 3000;
maxDist = 6000;
HTMLElements = []; HTMLElements = [];
div; div;
scroll; scroll;
@ -25,11 +26,21 @@ class InfiniteScroller {
this.div = div; this.div = div;
//this.interval=setInterval(this.updatestuff.bind(this,true),100); //this.interval=setInterval(this.updatestuff.bind(this,true),100);
this.scroll = scroll; this.scroll = scroll;
this.div.addEventListener("scroll", this.watchForChange.bind(this)); this.div.addEventListener("scroll", _ => {
this.scroll.addEventListener("scroll", this.watchForChange.bind(this)); if (this.scroll)
this.scrollTop = this.scroll.scrollTop;
this.watchForChange();
});
this.scroll.addEventListener("scroll", _ => {
if (null === this.timeout) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
}
this.watchForChange();
});
{ {
let oldheight = 0; let oldheight = 0;
new ResizeObserver(_ => { new ResizeObserver(_ => {
this.updatestuff();
const change = oldheight - div.offsetHeight; const change = oldheight - div.offsetHeight;
if (change > 0 && this.scroll) { if (change > 0 && this.scroll) {
this.scroll.scrollTop += change; this.scroll.scrollTop += change;
@ -49,11 +60,16 @@ class InfiniteScroller {
scrollBottom; scrollBottom;
scrollTop; scrollTop;
needsupdate = true; needsupdate = true;
averageheight = 60;
async updatestuff() { async updatestuff() {
this.timeout = null;
if (!this.scroll) if (!this.scroll)
return; return;
this.timeout = null;
this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight; this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight;
this.averageheight = this.scroll.scrollHeight / this.HTMLElements.length;
if (this.averageheight < 10) {
this.averageheight = 60;
}
this.scrollTop = this.scroll.scrollTop; this.scrollTop = this.scroll.scrollTop;
if (!this.scrollBottom) { if (!this.scrollBottom) {
if (!await this.watchForChange()) { if (!await this.watchForChange()) {
@ -84,120 +100,160 @@ class InfiniteScroller {
const scrollBottom = this.scrollBottom; const scrollBottom = this.scrollBottom;
return () => { return () => {
if (this.scroll && scrollBottom < 30) { if (this.scroll && scrollBottom < 30) {
this.scroll.scrollTop = this.scroll.scrollHeight; this.scroll.scrollTop = this.scroll.scrollHeight + 20;
} }
}; };
} }
async watchForTop() { async watchForTop(already = false, fragement = new DocumentFragment()) {
if (!this.scroll) if (!this.scroll)
return false; return false;
let again = false; try {
if (this.scrollTop === 0) { let again = false;
this.scrollTop = 1; if (this.scrollTop < (already ? this.fillDist : this.minDist)) {
this.scroll.scrollTop = 1; let nextid;
} const firstelm = this.HTMLElements.at(0);
if (this.scrollTop < this.minDist) { if (firstelm) {
let nextid; const previd = firstelm[1];
const firstelm = this.HTMLElements.at(0); nextid = await this.getIDFromOffset(previd, 1);
if (firstelm) {
const previd = firstelm[1];
nextid = await this.getIDFromOffset(previd, 1);
}
if (!nextid) {
}
else {
again = true;
const html = await this.getHTMLFromID(nextid);
if (!html) {
this.destroyFromID(nextid);
console.error("html isn't defined");
throw Error("html isn't defined");
} }
this.scroll.prepend(html); if (!nextid) {
this.HTMLElements.unshift([html, nextid]); }
this.scrollTop += 60; else {
const html = await this.getHTMLFromID(nextid);
if (!html) {
this.destroyFromID(nextid);
return false;
}
again = true;
fragement.prepend(html);
this.HTMLElements.unshift([html, nextid]);
this.scrollTop += this.averageheight;
}
;
} }
; if (this.scrollTop > this.maxDist) {
const html = this.HTMLElements.shift();
if (html) {
again = true;
await this.destroyFromID(html[1]);
this.scrollTop -= this.averageheight;
}
}
if (again) {
await this.watchForTop(true, fragement);
}
return again;
} }
if (this.scrollTop > this.maxDist) { finally {
const html = this.HTMLElements.shift(); if (!already) {
if (html) { if (this.scroll.scrollTop === 0) {
again = true; this.scrollTop = 1;
await this.destroyFromID(html[1]); this.scroll.scrollTop = 10;
this.scrollTop -= 60; }
this.scroll.prepend(fragement, fragement);
} }
} }
if (again) {
await this.watchForTop();
}
return again;
} }
async watchForBottom() { async watchForBottom(already = false, fragement = new DocumentFragment()) {
if (!this.scroll) if (!this.scroll)
return false; return false;
let again = false; try {
const scrollBottom = this.scrollBottom; let again = false;
if (scrollBottom < this.minDist) { const scrollBottom = this.scrollBottom;
let nextid; if (scrollBottom < (already ? this.fillDist : this.minDist)) {
const lastelm = this.HTMLElements.at(-1); let nextid;
if (lastelm) { const lastelm = this.HTMLElements.at(-1);
const previd = lastelm[1]; if (lastelm) {
nextid = await this.getIDFromOffset(previd, -1); const previd = lastelm[1];
nextid = await this.getIDFromOffset(previd, -1);
}
if (!nextid) {
}
else {
again = true;
const html = await this.getHTMLFromID(nextid);
fragement.appendChild(html);
this.HTMLElements.push([html, nextid]);
this.scrollBottom += this.averageheight;
}
;
} }
if (!nextid) { if (scrollBottom > this.maxDist) {
const html = this.HTMLElements.pop();
if (html) {
await this.destroyFromID(html[1]);
this.scrollBottom -= this.averageheight;
again = true;
}
} }
else { if (again) {
again = true; await this.watchForBottom(true, fragement);
const html = await this.getHTMLFromID(nextid); }
this.scroll.appendChild(html); return again;
this.HTMLElements.push([html, nextid]); }
this.scrollBottom += 60; finally {
if (scrollBottom < 30) { if (!already) {
this.scroll.append(fragement);
if (this.scrollBottom < 30) {
this.scroll.scrollTop = this.scroll.scrollHeight; this.scroll.scrollTop = this.scroll.scrollHeight;
} }
} }
;
} }
if (scrollBottom > this.maxDist) {
const html = this.HTMLElements.pop();
if (html) {
await this.destroyFromID(html[1]);
this.scrollBottom -= 60;
again = true;
}
}
if (again) {
await this.watchForBottom();
}
return again;
} }
watchtime = false;
changePromise;
async watchForChange() { async watchForChange() {
try { if (this.currrunning) {
if (this.currrunning) { this.watchtime = true;
return false; if (this.changePromise) {
return await this.changePromise;
} }
else { else {
this.currrunning = true; return true;
} }
if (!this.div) { }
this.currrunning = false; else {
this.watchtime = false;
this.currrunning = true;
}
this.changePromise = new Promise(async (res) => {
try {
try {
if (!this.div) {
res(false);
return false;
}
const out = await Promise.allSettled([this.watchForTop(), this.watchForBottom()]);
const changed = (out[0].value || out[1].value);
if (null === this.timeout && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
}
if (!this.currrunning) {
console.error("something really bad happened");
}
res(!!changed);
return !!changed;
}
catch (e) {
console.error(e);
}
res(false);
return false; return false;
} }
const out = await Promise.allSettled([this.watchForTop(), this.watchForBottom()]); catch (e) {
const changed = (out[0].value || out[1].value); throw e;
if (null === this.timeout && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
if (!this.currrunning) { finally {
console.error("something really bad happened"); this.changePromise = undefined;
setTimeout(_ => {
this.currrunning = false;
if (this.watchtime) {
this.watchForChange();
}
}, 300);
} }
this.currrunning = false; });
return !!changed; return await this.changePromise;
}
catch (e) {
console.error(e);
}
return false;
} }
async focus(id, flash = true) { async focus(id, flash = true) {
let element; let element;
@ -206,6 +262,7 @@ class InfiniteScroller {
element = thing[0]; element = thing[0];
} }
} }
console.log(id, element, this.HTMLElements.length, ":3");
if (element) { if (element) {
if (flash) { if (flash) {
element.scrollIntoView({ element.scrollIntoView({

View file

@ -89,6 +89,11 @@ class Localuser {
const guildid = guild.snowflake; const guildid = guild.snowflake;
this.guildids.get(guildid.id).channelids[thing.channel_id].readStateInfo(thing); this.guildids.get(guildid.id).channelids[thing.channel_id].readStateInfo(thing);
} }
for (const thing of ready.d.relationships) {
const user = new User(thing.user, this);
user.nickname = thing.nickname;
user.relationshipType = thing.type;
}
} }
outoffocus() { outoffocus() {
const servers = document.getElementById("servers"); const servers = document.getElementById("servers");

View file

@ -22,7 +22,6 @@ function trimswitcher() {
const map = new Map(); const map = new Map();
for (const thing in json.users) { for (const thing in json.users) {
const user = json.users[thing]; const user = json.users[thing];
console.log(user, json.users);
let wellknown = user.serverurls.wellknown; let wellknown = user.serverurls.wellknown;
if (wellknown[wellknown.length - 1] !== "/") { if (wellknown[wellknown.length - 1] !== "/") {
wellknown += "/"; wellknown += "/";

View file

@ -1,6 +1,5 @@
import { User } from "./user.js"; import { User } from "./user.js";
import { Role } from "./role.js"; import { Role } from "./role.js";
import { Contextmenu } from "./contextmenu.js";
import { SnowFlake } from "./snowflake.js"; import { SnowFlake } from "./snowflake.js";
class Member { class Member {
static already = {}; static already = {};
@ -9,18 +8,6 @@ class Member {
roles = []; roles = [];
id; id;
nick; nick;
static contextmenu = new Contextmenu("User Menu");
static setUpContextMenu() {
this.contextmenu.addbutton("Copy user id", function () {
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user", function () {
fetch(this.info.api + "/users/@me/channels", { method: "POST",
body: JSON.stringify({ "recipients": [this.id] }),
headers: this.localuser.headers
});
});
}
constructor(memberjson, owner) { constructor(memberjson, owner) {
if (User.userids[memberjson.id]) { if (User.userids[memberjson.id]) {
this.user = User.userids[memberjson.id]; this.user = User.userids[memberjson.id];
@ -169,12 +156,10 @@ class Member {
*/ */
html.style.color = this.getColor(); html.style.color = this.getColor();
} }
this.profileclick(html); //this.profileclick(html);
Member.contextmenu.bind(html);
} }
profileclick(html) { profileclick(html) {
//to be implemented //to be implemented
} }
} }
Member.setUpContextMenu();
export { Member }; export { Member };

View file

@ -49,7 +49,7 @@ class Message {
Message.contextmenu.addbutton("Copy raw text", function () { Message.contextmenu.addbutton("Copy raw text", function () {
navigator.clipboard.writeText(this.content.rawString); navigator.clipboard.writeText(this.content.rawString);
}); });
Message.contextmenu.addbutton("Reply", function (div) { Message.contextmenu.addbutton("Reply", function () {
this.channel.setReplying(this); this.channel.setReplying(this);
}); });
Message.contextmenu.addbutton("Copy message id", function () { Message.contextmenu.addbutton("Copy message id", function () {
@ -65,10 +65,14 @@ class Message {
const markdown = document.getElementById("typebox")["markdown"]; const markdown = document.getElementById("typebox")["markdown"];
markdown.txt = this.content.rawString.split(''); markdown.txt = this.content.rawString.split('');
markdown.boxupdate(document.getElementById("typebox")); markdown.boxupdate(document.getElementById("typebox"));
}, null, _ => { return _.author.id === _.localuser.user.id; }); }, null, function () {
return this.author.id === this.localuser.user.id;
});
Message.contextmenu.addbutton("Delete message", function () { Message.contextmenu.addbutton("Delete message", function () {
this.delete(); this.delete();
}, null, _ => { return _.canDelete(); }); }, null, function () {
return this.canDelete();
});
} }
constructor(messagejson, owner) { constructor(messagejson, owner) {
this.owner = owner; this.owner = owner;
@ -169,7 +173,7 @@ class Message {
return this.owner.info; return this.owner.info;
} }
messageevents(obj) { messageevents(obj) {
const func = Message.contextmenu.bind(obj, this); const func = Message.contextmenu.bindContextmenu(obj, this, undefined);
this.div = obj; this.div = obj;
obj.classList.add("messagediv"); obj.classList.add("messagediv");
} }
@ -245,7 +249,16 @@ class Message {
} }
} }
reactdiv; reactdiv;
generateMessage(premessage = undefined) { blockedPropigate() {
const premessage = this.channel.idToPrev.get(this.snowflake)?.getObject();
if (premessage?.author === this.author) {
premessage.blockedPropigate();
}
else {
this.generateMessage();
}
}
generateMessage(premessage = undefined, ignoredblock = false) {
if (!this.div) if (!this.div)
return; return;
if (!premessage) { if (!premessage) {
@ -257,7 +270,66 @@ class Message {
} }
div.innerHTML = ""; div.innerHTML = "";
const build = document.createElement('div'); const build = document.createElement('div');
build.classList.add("flexltr"); build.classList.add("flexltr", "message");
div.classList.remove("zeroheight");
if (this.author.relationshipType === 2) {
if (ignoredblock) {
if (premessage?.author !== this.author) {
const span = document.createElement("span");
span.textContent = `You have this user blocked, click to hide these messages.`;
div.append(span);
span.classList.add("blocked");
span.onclick = _ => {
const scroll = this.channel.infinite.scrollTop;
let next = this;
while (next?.author === this.author) {
next.generateMessage(undefined);
next = this.channel.idToNext.get(next.snowflake)?.getObject();
}
if (this.channel.infinite.scroll && scroll) {
this.channel.infinite.scroll.scrollTop = scroll;
}
};
}
}
else {
div.classList.remove("topMessage");
if (premessage?.author === this.author) {
div.classList.add("zeroheight");
premessage.blockedPropigate();
div.appendChild(build);
return div;
}
else {
build.classList.add("blocked", "topMessage");
const span = document.createElement("span");
let count = 1;
let next = this.channel.idToNext.get(this.snowflake)?.getObject();
while (next?.author === this.author) {
count++;
next = this.channel.idToNext.get(next.snowflake)?.getObject();
}
span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`;
build.append(span);
span.onclick = _ => {
const scroll = this.channel.infinite.scrollTop;
const func = this.channel.infinite.snapBottom();
let next = this;
while (next?.author === this.author) {
next.generateMessage(undefined, true);
next = this.channel.idToNext.get(next.snowflake)?.getObject();
console.log("loopy");
}
if (this.channel.infinite.scroll && scroll) {
func();
this.channel.infinite.scroll.scrollTop = scroll;
}
};
div.appendChild(build);
return div;
}
}
}
if (this.message_reference) { if (this.message_reference) {
const replyline = document.createElement("div"); const replyline = document.createElement("div");
const line = document.createElement("hr"); const line = document.createElement("hr");
@ -269,7 +341,6 @@ class Message {
replyline.appendChild(username); replyline.appendChild(username);
const reply = document.createElement("div"); const reply = document.createElement("div");
username.classList.add("username"); username.classList.add("username");
this.author.bind(username, this.guild);
reply.classList.add("replytext"); reply.classList.add("replytext");
replyline.appendChild(reply); replyline.appendChild(reply);
const line2 = document.createElement("hr"); const line2 = document.createElement("hr");
@ -278,6 +349,10 @@ class Message {
line.classList.add("startreply"); line.classList.add("startreply");
replyline.classList.add("replyflex"); replyline.classList.add("replyflex");
this.channel.getmessage(this.message_reference.message_id).then(message => { this.channel.getmessage(this.message_reference.message_id).then(message => {
if (message.author.relationshipType === 2) {
username.textContent = "Blocked user";
return;
}
const author = message.author; const author = message.author;
reply.appendChild(message.content.makeHTML({ stdsize: true })); reply.appendChild(message.content.makeHTML({ stdsize: true }));
minipfp.src = author.getpfpsrc(); minipfp.src = author.getpfpsrc();
@ -290,7 +365,6 @@ class Message {
}; };
div.appendChild(replyline); div.appendChild(replyline);
} }
build.classList.add("message");
div.appendChild(build); div.appendChild(build);
if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) {
const pfpRow = document.createElement('div'); const pfpRow = document.createElement('div');
@ -491,21 +565,17 @@ class Message {
} }
} }
} }
let now = new Date().toLocaleDateString();
const yesterday = new Date(now);
yesterday.setDate(new Date().getDate() - 1);
let yesterdayStr = yesterday.toLocaleDateString();
function formatTime(date) { function formatTime(date) {
const now = new Date(); const datestring = date.toLocaleDateString();
const sameDay = date.getDate() === now.getDate() && const formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
date.getMonth() === now.getMonth() && if (datestring === now) {
date.getFullYear() === now.getFullYear();
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
const isYesterday = date.getDate() === yesterday.getDate() &&
date.getMonth() === yesterday.getMonth() &&
date.getFullYear() === yesterday.getFullYear();
const formatTime = date => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
if (sameDay) {
return `Today at ${formatTime(date)}`; return `Today at ${formatTime(date)}`;
} }
else if (isYesterday) { else if (datestring === yesterdayStr) {
return `Yesterday at ${formatTime(date)}`; return `Yesterday at ${formatTime(date)}`;
} }
else { else {

View file

@ -10,6 +10,8 @@ class User {
snowflake; snowflake;
avatar; avatar;
username; username;
nickname = null;
relationshipType = 0;
bio; bio;
discriminator; discriminator;
pronouns; pronouns;
@ -75,6 +77,25 @@ class User {
headers: this.localuser.headers headers: this.localuser.headers
}); });
}); });
this.contextmenu.addbutton("Block user", function () {
this.block();
}, null, function () {
return this.relationshipType !== 2;
});
this.contextmenu.addbutton("Unblock user", function () {
this.unblock();
}, null, function () {
return this.relationshipType === 2;
});
this.contextmenu.addbutton("Friend request", function () {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT",
headers: this.owner.headers,
body: JSON.stringify({
type: 1
})
});
});
} }
static clear() { static clear() {
this.userids = {}; this.userids = {};
@ -148,6 +169,7 @@ class User {
} }
buildpfp() { buildpfp() {
const pfp = document.createElement('img'); const pfp = document.createElement('img');
pfp.loading = "lazy";
pfp.src = this.getpfpsrc(); pfp.src = this.getpfpsrc();
pfp.classList.add("pfp"); pfp.classList.add("pfp");
pfp.classList.add("userid:" + this.id); pfp.classList.add("userid:" + this.id);
@ -183,6 +205,7 @@ class User {
bind(html, guild = null, error = true) { bind(html, guild = null, error = true) {
if (guild && guild.id !== "@me") { if (guild && guild.id !== "@me") {
Member.resolveMember(this, guild).then(_ => { Member.resolveMember(this, guild).then(_ => {
User.contextmenu.bindContextmenu(html, this, _);
if (_ === undefined && error) { if (_ === undefined && error) {
const error = document.createElement("span"); const error = document.createElement("span");
error.textContent = "!"; error.textContent = "!";
@ -203,7 +226,6 @@ class User {
else { else {
this.profileclick(html); this.profileclick(html);
} }
User.contextmenu.bind(html, this);
} }
static async resolve(id, localuser) { static async resolve(id, localuser) {
const json = await fetch(localuser.info.api.toString() + "/users/" + id + "/profile", { headers: localuser.headers }).then(_ => _.json()); const json = await fetch(localuser.info.api.toString() + "/users/" + id + "/profile", { headers: localuser.headers }).then(_ => _.json());
@ -218,6 +240,35 @@ class User {
thing.src = src; thing.src = src;
} }
} }
block() {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT",
headers: this.owner.headers,
body: JSON.stringify({
type: 2
})
});
this.relationshipType = 2;
const channel = this.localuser.channelfocus;
if (channel) {
for (const thing of channel.messages) {
thing[1].generateMessage();
}
}
}
unblock() {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE",
headers: this.owner.headers,
});
this.relationshipType = 0;
const channel = this.localuser.channelfocus;
if (channel) {
for (const thing of channel.messages) {
thing[1].generateMessage();
}
}
}
getpfpsrc() { getpfpsrc() {
if (this.hypotheticalpfp) { if (this.hypotheticalpfp) {
return this.avatar; return this.avatar;

View file

@ -43,7 +43,7 @@ class Channel{
typing:number; typing:number;
message_notifications:number; message_notifications:number;
allthewayup:boolean; allthewayup:boolean;
static contextmenu=new Contextmenu("channel menu"); static contextmenu=new Contextmenu<Channel,undefined>("channel menu");
replyingto:Message|null; replyingto:Message|null;
infinite:InfiniteScroller; infinite:InfiniteScroller;
idToPrev:Map<SnowFlake<Message>,SnowFlake<Message>>=new Map(); idToPrev:Map<SnowFlake<Message>,SnowFlake<Message>>=new Map();
@ -70,16 +70,16 @@ class Channel{
this.contextmenu.addbutton("Delete channel",function(this:Channel){ this.contextmenu.addbutton("Delete channel",function(this:Channel){
console.log(this) console.log(this)
this.deleteChannel(); this.deleteChannel();
},null,_=>{console.log(_);return _.isAdmin()}); },null,function(){return this.isAdmin()});
this.contextmenu.addbutton("Edit channel",function(this:Channel){ this.contextmenu.addbutton("Edit channel",function(this:Channel){
this.editChannel(); this.editChannel();
},null,_=>{return _.isAdmin()}); },null,function(){return this.isAdmin()});
this.contextmenu.addbutton("Make invite",function(this:Channel){ this.contextmenu.addbutton("Make invite",function(this:Channel){
this.createInvite(); this.createInvite();
},null,(_:Channel)=>{ },null,function(){
return _.hasPermission("CREATE_INSTANT_INVITE")&&_.type!==4 return this.hasPermission("CREATE_INSTANT_INVITE")&&this.type!==4
}); });
/* /*
this.contextmenu.addbutton("Test button",function(){ this.contextmenu.addbutton("Test button",function(){
@ -170,7 +170,7 @@ class Channel{
} }
setUpInfiniteScroller(){ setUpInfiniteScroller(){
this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise<string|undefined>{ this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise<string|undefined>{
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>; const snowflake=(this.messages.get(id) as Message).snowflake;
if(offset===1){ if(offset===1){
if(this.idToPrev.has(snowflake)){ if(this.idToPrev.has(snowflake)){
return this.idToPrev.get(snowflake)?.id; return this.idToPrev.get(snowflake)?.id;
@ -196,13 +196,15 @@ class Channel{
if(messgage){ if(messgage){
const html=messgage.buildhtml(); const html=messgage.buildhtml();
return html; return html;
}else{
console.error(id+" not found")
} }
}catch(e){ }catch(e){
console.error(e); console.error(e);
} }
}.bind(this), }.bind(this),
async function(this:Channel,id:string){ async function(this:Channel,id:string){
const message=SnowFlake.getSnowFlakeFromID(id,Message).getObject(); const message=this.messages.get(id);
try{ try{
if(message){ if(message){
message.deleteDiv(); message.deleteDiv();
@ -383,7 +385,7 @@ class Channel{
decdiv.classList.add("channeleffects"); decdiv.classList.add("channeleffects");
decdiv.classList.add("channel"); decdiv.classList.add("channel");
Channel.contextmenu.bind(decdiv,this); Channel.contextmenu.bindContextmenu(decdiv,this,undefined);
decdiv["all"]=this; decdiv["all"]=this;
@ -409,7 +411,7 @@ class Channel{
if(this.hasunreads){ if(this.hasunreads){
div.classList.add("cunread"); div.classList.add("cunread");
} }
Channel.contextmenu.bind(div,this); Channel.contextmenu.bindContextmenu(div,this,undefined);
if(admin){this.coatDropDiv(div);} if(admin){this.coatDropDiv(div);}
div["all"]=this; div["all"]=this;
const myhtml=document.createElement("span"); const myhtml=document.createElement("span");
@ -817,12 +819,6 @@ class Channel{
async grabArround(id:string){//currently unused and no plans to use it yet async grabArround(id:string){//currently unused and no plans to use it yet
throw new Error("please don't call this, no one has implemented it :P") throw new Error("please don't call this, no one has implemented it :P")
} }
buildmessage(message:Message,next:Message){
const built=message.buildhtml(next);
if(built){
(document.getElementById("messages") as HTMLDivElement).prepend(built);
}
}
async buildmessages(){ async buildmessages(){
/* /*
if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){ if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){

View file

@ -1,7 +1,7 @@
class Contextmenu{ class Contextmenu<x,y>{
static currentmenu; static currentmenu;
name:string; name:string;
buttons:[string,Function,string|null,Function,Function,string][]; buttons:[string,(e:MouseEvent)=>void,string|null,(this:x,arg:y)=>boolean,(this:x,arg:y)=>boolean,string][];
div:HTMLDivElement; div:HTMLDivElement;
static setup(){ static setup(){
Contextmenu.currentmenu=""; Contextmenu.currentmenu="";
@ -19,28 +19,28 @@ class Contextmenu{
this.name=name; this.name=name;
this.buttons=[] this.buttons=[]
} }
addbutton(text:string,onclick:Function,img:null|string=null,shown=_=>true,enabled=_=>true){ addbutton(text:string,onclick:(e:MouseEvent)=>void,img:null|string=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){
this.buttons.push([text,onclick,img,shown,enabled,"button"]); this.buttons.push([text,onclick,img,shown,enabled,"button"]);
return {}; return {};
} }
addsubmenu(text:string,onclick:(e:MouseEvent)=>void,img=null,shown=_=>true,enabled=_=>true){ addsubmenu(text:string,onclick:(e:MouseEvent)=>void,img=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){
this.buttons.push([text,onclick,img,shown,enabled,"submenu"]) this.buttons.push([text,onclick,img,shown,enabled,"submenu"])
return {}; return {};
} }
makemenu(x:number,y:number,addinfo:any,obj:HTMLElement){ makemenu(x:number,y:number,addinfo:any,other:y){
const div=document.createElement("div"); const div=document.createElement("div");
div.classList.add("contextmenu","flexttb"); div.classList.add("contextmenu","flexttb");
for(const thing of this.buttons){ for(const thing of this.buttons){
if(!thing[3](addinfo)){continue;} if(!thing[3].bind(addinfo)(other)){continue;}
const intext=document.createElement("button") const intext=document.createElement("button")
intext.disabled=!thing[4](); intext.disabled=!thing[4].bind(addinfo)(other);
intext.classList.add("contextbutton") intext.classList.add("contextbutton")
intext.textContent=thing[0] intext.textContent=thing[0]
console.log(thing) console.log(thing)
if(thing[5]==="button"){ if(thing[5]==="button"){
intext.onclick=thing[1].bind(addinfo,obj); intext.onclick=thing[1].bind(addinfo,other);
}else if(thing[5]==="submenu"){ }else if(thing[5]==="submenu"){
intext.onclick=thing[1].bind(addinfo); intext.onclick=thing[1].bind(addinfo,other);
} }
div.appendChild(intext); div.appendChild(intext);
@ -56,11 +56,11 @@ class Contextmenu{
Contextmenu.currentmenu=div; Contextmenu.currentmenu=div;
return this.div; return this.div;
} }
bind(obj:HTMLElement,addinfo:any=undefined){ bindContextmenu(obj:HTMLElement,addinfo:x,other:y){
const func=(event) => { const func=(event) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.makemenu(event.clientX,event.clientY,addinfo,obj) this.makemenu(event.clientX,event.clientY,addinfo,other);
} }
obj.addEventListener("contextmenu", func); obj.addEventListener("contextmenu", func);
return func; return func;

View file

@ -30,7 +30,7 @@ class Guild{
get id(){ get id(){
return this.snowflake.id; return this.snowflake.id;
} }
static contextmenu=new Contextmenu("guild menu"); static contextmenu=new Contextmenu<Guild,undefined>("guild menu");
static setupcontextmenu(){ static setupcontextmenu(){
Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){ Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){
console.log(this) console.log(this)
@ -49,11 +49,15 @@ class Guild{
Guild.contextmenu.addbutton("Leave guild",function(this:Guild){ Guild.contextmenu.addbutton("Leave guild",function(this:Guild){
this.confirmleave(); this.confirmleave();
},null,function(_){return _.properties.owner_id!==_.member.user.id}); },null,function(_){
return this.properties.owner_id!==this.member.user.id
});
Guild.contextmenu.addbutton("Delete guild",function(this:Guild){ Guild.contextmenu.addbutton("Delete guild",function(this:Guild){
this.confirmDelete(); this.confirmDelete();
},null,function(_){return _.properties.owner_id===_.member.user.id}); },null,function(_){
return this.properties.owner_id===this.member.user.id
});
Guild.contextmenu.addbutton("Create invite",function(this:Guild){ Guild.contextmenu.addbutton("Create invite",function(this:Guild){
console.log(this); console.log(this);
@ -282,7 +286,7 @@ class Guild{
this.loadGuild(); this.loadGuild();
this.loadChannel(); this.loadChannel();
} }
Guild.contextmenu.bind(img,this); Guild.contextmenu.bindContextmenu(img,this,undefined);
}else{ }else{
const div=document.createElement("div"); const div=document.createElement("div");
let build=this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, ""); let build=this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, "");
@ -293,7 +297,7 @@ class Guild{
this.loadGuild(); this.loadGuild();
this.loadChannel(); this.loadChannel();
} }
Guild.contextmenu.bind(div,this) Guild.contextmenu.bindContextmenu(div,this,undefined)
} }
return divy; return divy;
} }

View file

@ -114,7 +114,7 @@ try{
{ {
const menu=new Contextmenu("create rightclick"); const menu=new Contextmenu("create rightclick");//Really should go into the localuser class, but that's a later thing
menu.addbutton("Create channel",function(){ menu.addbutton("Create channel",function(){
if(thisuser.lookingguild){ if(thisuser.lookingguild){
thisuser.lookingguild.createchannels(); thisuser.lookingguild.createchannels();
@ -126,7 +126,7 @@ try{
thisuser.lookingguild.createcategory(); thisuser.lookingguild.createcategory();
} }
},null,_=>{return thisuser.isAdmin()}) },null,_=>{return thisuser.isAdmin()})
menu.bind(document.getElementById("channels") as HTMLDivElement) menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement,0,0)
} }
const pasteimage=document.getElementById("pasteimage") as HTMLDivElement; const pasteimage=document.getElementById("pasteimage") as HTMLDivElement;

View file

@ -3,8 +3,9 @@ class InfiniteScroller{
readonly getHTMLFromID:(ID:string)=>Promise<HTMLElement>; readonly getHTMLFromID:(ID:string)=>Promise<HTMLElement>;
readonly destroyFromID:(ID:string)=>Promise<boolean>; readonly destroyFromID:(ID:string)=>Promise<boolean>;
readonly reachesBottom:()=>void; readonly reachesBottom:()=>void;
private readonly minDist=3000; private readonly minDist=2000;
private readonly maxDist=8000; private readonly fillDist=3000;
private readonly maxDist=6000;
HTMLElements:[HTMLElement,string][]=[]; HTMLElements:[HTMLElement,string][]=[];
div:HTMLDivElement|null; div:HTMLDivElement|null;
scroll:HTMLDivElement|null; scroll:HTMLDivElement|null;
@ -26,11 +27,21 @@ class InfiniteScroller{
//this.interval=setInterval(this.updatestuff.bind(this,true),100); //this.interval=setInterval(this.updatestuff.bind(this,true),100);
this.scroll=scroll; this.scroll=scroll;
this.div.addEventListener("scroll",this.watchForChange.bind(this)); this.div.addEventListener("scroll",_=>{
this.scroll.addEventListener("scroll",this.watchForChange.bind(this)); if(this.scroll) this.scrollTop=this.scroll.scrollTop;
this.watchForChange()
});
this.scroll.addEventListener("scroll",_=>{
if(null===this.timeout){
this.timeout=setTimeout(this.updatestuff.bind(this),300);
}
this.watchForChange()
});
{ {
let oldheight=0; let oldheight=0;
new ResizeObserver(_=>{ new ResizeObserver(_=>{
this.updatestuff();
const change=oldheight-div.offsetHeight; const change=oldheight-div.offsetHeight;
if(change>0&&this.scroll){ if(change>0&&this.scroll){
this.scroll.scrollTop+=change; this.scroll.scrollTop+=change;
@ -51,10 +62,15 @@ class InfiniteScroller{
scrollBottom:number; scrollBottom:number;
scrollTop:number; scrollTop:number;
needsupdate=true; needsupdate=true;
averageheight:number=60;
async updatestuff(){ async updatestuff(){
if(!this.scroll) return;
this.timeout=null; this.timeout=null;
if(!this.scroll) return;
this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight; this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight;
this.averageheight=this.scroll.scrollHeight/this.HTMLElements.length;
if(this.averageheight<10){
this.averageheight=60;
}
this.scrollTop=this.scroll.scrollTop; this.scrollTop=this.scroll.scrollTop;
if(!this.scrollBottom){ if(!this.scrollBottom){
if(!await this.watchForChange()){ if(!await this.watchForChange()){
@ -84,124 +100,166 @@ class InfiniteScroller{
const scrollBottom=this.scrollBottom; const scrollBottom=this.scrollBottom;
return ()=>{ return ()=>{
if(this.scroll&&scrollBottom<30){ if(this.scroll&&scrollBottom<30){
this.scroll.scrollTop=this.scroll.scrollHeight; this.scroll.scrollTop=this.scroll.scrollHeight+20;
} }
} }
} }
private async watchForTop():Promise<boolean>{ private async watchForTop(already=false,fragement=new DocumentFragment()):Promise<boolean>{
if(!this.scroll) return false; if(!this.scroll) return false;
let again=false; try{
if(this.scrollTop===0){ let again=false;
this.scrollTop=1; if(this.scrollTop<(already?this.fillDist:this.minDist)){
this.scroll.scrollTop=1; let nextid:string|undefined;
} const firstelm=this.HTMLElements.at(0);
if(this.scrollTop<this.minDist){ if(firstelm){
let nextid:string|undefined; const previd=firstelm[1];
const firstelm=this.HTMLElements.at(0); nextid=await this.getIDFromOffset(previd,1);
if(firstelm){
const previd=firstelm[1];
nextid=await this.getIDFromOffset(previd,1);
}
if(!nextid){
}else{
again=true;
const html=await this.getHTMLFromID(nextid);
if(!html){
this.destroyFromID(nextid);
console.error("html isn't defined");
throw Error("html isn't defined");
} }
this.scroll.prepend(html);
this.HTMLElements.unshift([html,nextid]);
this.scrollTop+=60;
};
}
if(this.scrollTop>this.maxDist){
const html=this.HTMLElements.shift(); if(!nextid){
if(html){
again=true; }else{
await this.destroyFromID(html[1]); const html=await this.getHTMLFromID(nextid);
this.scrollTop-=60; if(!html){
this.destroyFromID(nextid);
return false;
}
again=true;
fragement.prepend(html);
this.HTMLElements.unshift([html,nextid]);
this.scrollTop+=this.averageheight;
};
}
if(this.scrollTop>this.maxDist){
const html=this.HTMLElements.shift();
if(html){
again=true;
await this.destroyFromID(html[1]);
this.scrollTop-=this.averageheight;
}
}
if(again){
await this.watchForTop(true,fragement);
}
return again;
}finally{
if(!already){
if(this.scroll.scrollTop===0){
this.scrollTop=1;
this.scroll.scrollTop=10;
}
this.scroll.prepend(fragement,fragement);
} }
} }
if(again){
await this.watchForTop();
}
return again;
} }
async watchForBottom():Promise<boolean>{ async watchForBottom(already=false,fragement=new DocumentFragment()):Promise<boolean>{
if(!this.scroll) return false; if(!this.scroll) return false;
let again=false; try{
const scrollBottom = this.scrollBottom; let again=false;
if(scrollBottom<this.minDist){ const scrollBottom = this.scrollBottom;
if(scrollBottom<(already?this.fillDist:this.minDist)){
let nextid:string|undefined; let nextid:string|undefined;
const lastelm=this.HTMLElements.at(-1); const lastelm=this.HTMLElements.at(-1);
if(lastelm){ if(lastelm){
const previd=lastelm[1]; const previd=lastelm[1];
nextid=await this.getIDFromOffset(previd,-1); nextid=await this.getIDFromOffset(previd,-1);
}
if(!nextid){
}else{
again=true;
const html=await this.getHTMLFromID(nextid);
fragement.appendChild(html);
this.HTMLElements.push([html,nextid]);
this.scrollBottom+=this.averageheight;
};
} }
if(!nextid){ if(scrollBottom>this.maxDist){
}else{
again=true;
const html=await this.getHTMLFromID(nextid); const html=this.HTMLElements.pop();
this.scroll.appendChild(html); if(html){
this.HTMLElements.push([html,nextid]); await this.destroyFromID(html[1]);
this.scrollBottom+=60; this.scrollBottom-=this.averageheight;
if(scrollBottom<30){ again=true;
}
}
if(again){
await this.watchForBottom(true,fragement);
}
return again;
}finally{
if(!already){
this.scroll.append(fragement);
if(this.scrollBottom<30){
this.scroll.scrollTop=this.scroll.scrollHeight; this.scroll.scrollTop=this.scroll.scrollHeight;
} }
};
}
if(scrollBottom>this.maxDist){
const html=this.HTMLElements.pop();
if(html){
await this.destroyFromID(html[1]);
this.scrollBottom-=60;
again=true;
} }
} }
if(again){
await this.watchForBottom();
}
return again;
} }
watchtime:boolean=false;
changePromise:Promise<boolean>|undefined;
async watchForChange():Promise<boolean>{ async watchForChange():Promise<boolean>{
try{
if(this.currrunning){ if(this.currrunning){
return false; this.watchtime=true;
if(this.changePromise){
return await this.changePromise;
}else{
return true;
}
}else{ }else{
this.watchtime=false;
this.currrunning=true; this.currrunning=true;
} }
if(!this.div){this.currrunning=false;return false} this.changePromise=new Promise<boolean>(async res=>{
const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[]; try{
const changed=(out[0].value||out[1].value);
if(null===this.timeout&&changed){ try{
this.timeout=setTimeout(this.updatestuff.bind(this),300); if(!this.div){res(false);return false}
} const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[];
if(!this.currrunning){console.error("something really bad happened")} const changed=(out[0].value||out[1].value);
this.currrunning=false; if(null===this.timeout&&changed){
return !!changed; this.timeout=setTimeout(this.updatestuff.bind(this),300);
}catch(e){ }
console.error(e); if(!this.currrunning){console.error("something really bad happened")}
}
return false; res(!!changed);
return !!changed;
}catch(e){
console.error(e);
}
res(false);
return false;
}catch(e){
throw e;
}finally{
this.changePromise=undefined;
setTimeout(_=>{
this.currrunning=false;
if(this.watchtime){
this.watchForChange();
}
},300)
}
})
return await this.changePromise;
} }
async focus(id:string,flash=true){ async focus(id:string,flash=true){
let element:HTMLElement|undefined; let element:HTMLElement|undefined;
for(const thing of this.HTMLElements){ for(const thing of this.HTMLElements){
if(thing[1]===id){ if(thing[1]===id){
element=thing[0]; element=thing[0];
} }
} }
console.log(id,element,this.HTMLElements.length,":3");
if(element){ if(element){
if(flash){ if(flash){

View file

@ -44,7 +44,12 @@ type readyjson={
view_nsfw_guilds: boolean view_nsfw_guilds: boolean
}; };
guilds:guildjson[]; guilds:guildjson[];
relationships:any[]; relationships:{
id:string,
type:0|1|2|3|4,
nickname:string|null,
user:userjson
}[];
read_state:{ read_state:{
entries:{ entries:{
id: string, id: string,

View file

@ -95,6 +95,11 @@ class Localuser{
const guildid=guild.snowflake; const guildid=guild.snowflake;
(this.guildids.get(guildid.id) as Guild).channelids[thing.channel_id].readStateInfo(thing); (this.guildids.get(guildid.id) as Guild).channelids[thing.channel_id].readStateInfo(thing);
} }
for(const thing of ready.d.relationships){
const user=new User(thing.user,this);
user.nickname=thing.nickname;
user.relationshipType=thing.type;
}
} }
outoffocus():void{ outoffocus():void{
const servers=document.getElementById("servers") as HTMLDivElement; const servers=document.getElementById("servers") as HTMLDivElement;

View file

@ -23,7 +23,6 @@ function trimswitcher(){
const map=new Map(); const map=new Map();
for(const thing in json.users){ for(const thing in json.users){
const user=json.users[thing]; const user=json.users[thing];
console.log(user,json.users);
let wellknown=user.serverurls.wellknown; let wellknown=user.serverurls.wellknown;
if(wellknown[wellknown.length-1]!=="/"){ if(wellknown[wellknown.length-1]!=="/"){
wellknown+="/"; wellknown+="/";

View file

@ -1,7 +1,6 @@
import {User} from "./user.js"; import {User} from "./user.js";
import {Role} from "./role.js"; import {Role} from "./role.js";
import {Guild} from "./guild.js"; import {Guild} from "./guild.js";
import { Contextmenu } from "./contextmenu.js";
import { SnowFlake } from "./snowflake.js"; import { SnowFlake } from "./snowflake.js";
import { memberjson, presencejson, userjson } from "./jsontypes.js"; import { memberjson, presencejson, userjson } from "./jsontypes.js";
@ -12,19 +11,6 @@ class Member{
roles:Role[]=[]; roles:Role[]=[];
id:string; id:string;
nick:string; nick:string;
static contextmenu:Contextmenu=new Contextmenu("User Menu");
static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(this:Member){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user",function(this:Member){
fetch(this.info.api+"/users/@me/channels",
{method:"POST",
body:JSON.stringify({"recipients":[this.id]}),
headers: this.localuser.headers
});
});
}
private constructor(memberjson:memberjson,owner:Guild){ private constructor(memberjson:memberjson,owner:Guild){
if(User.userids[memberjson.id]){ if(User.userids[memberjson.id]){
this.user=User.userids[memberjson.id]; this.user=User.userids[memberjson.id];
@ -39,6 +25,7 @@ class Member{
if(thing==="owner"){continue} if(thing==="owner"){continue}
if(thing==="roles"){ if(thing==="roles"){
for(const strrole of memberjson["roles"]){ for(const strrole of memberjson["roles"]){
const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject(); const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject();
this.roles.push(role); this.roles.push(role);
} }
@ -160,12 +147,10 @@ class Member{
html.style.color=this.getColor(); html.style.color=this.getColor();
} }
this.profileclick(html); //this.profileclick(html);
Member.contextmenu.bind(html);
} }
profileclick(html:HTMLElement){ profileclick(html:HTMLElement){
//to be implemented //to be implemented
} }
} }
Member.setUpContextMenu();
export {Member}; export {Member};

View file

@ -12,7 +12,7 @@ import { memberjson, messagejson } from "./jsontypes.js";
import {Emoji} from "./emoji.js"; import {Emoji} from "./emoji.js";
class Message{ class Message{
static contextmenu=new Contextmenu("message menu"); static contextmenu=new Contextmenu<Message,undefined>("message menu");
owner:Channel; owner:Channel;
headers:Localuser["headers"]; headers:Localuser["headers"];
embeds:Embed[]; embeds:Embed[];
@ -54,7 +54,7 @@ class Message{
Message.contextmenu.addbutton("Copy raw text",function(this:Message){ Message.contextmenu.addbutton("Copy raw text",function(this:Message){
navigator.clipboard.writeText(this.content.rawString); navigator.clipboard.writeText(this.content.rawString);
}); });
Message.contextmenu.addbutton("Reply",function(this:Message,div:HTMLDivElement){ Message.contextmenu.addbutton("Reply",function(this:Message){
this.channel.setReplying(this); this.channel.setReplying(this);
}); });
Message.contextmenu.addbutton("Copy message id",function(this:Message){ Message.contextmenu.addbutton("Copy message id",function(this:Message){
@ -70,10 +70,15 @@ class Message{
const markdown=(document.getElementById("typebox") as HTMLDivElement)["markdown"] as MarkDown; const markdown=(document.getElementById("typebox") as HTMLDivElement)["markdown"] as MarkDown;
markdown.txt=this.content.rawString.split(''); markdown.txt=this.content.rawString.split('');
markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement);
},null,_=>{return _.author.id===_.localuser.user.id}); },null,function(){
return this.author.id===this.localuser.user.id
});
Message.contextmenu.addbutton("Delete message",function(this:Message){ Message.contextmenu.addbutton("Delete message",function(this:Message){
this.delete(); this.delete();
},null,_=>{return _.canDelete()}) },null,function(){
return this.canDelete()
})
} }
constructor(messagejson:messagejson,owner:Channel){ constructor(messagejson:messagejson,owner:Channel){
this.owner=owner; this.owner=owner;
@ -172,7 +177,7 @@ class Message{
return this.owner.info; return this.owner.info;
} }
messageevents(obj:HTMLDivElement){ messageevents(obj:HTMLDivElement){
const func=Message.contextmenu.bind(obj,this); const func=Message.contextmenu.bindContextmenu(obj,this,undefined);
this.div=obj; this.div=obj;
obj.classList.add("messagediv"); obj.classList.add("messagediv");
} }
@ -244,7 +249,15 @@ class Message{
} }
} }
reactdiv:WeakRef<HTMLDivElement>; reactdiv:WeakRef<HTMLDivElement>;
generateMessage(premessage:Message|undefined=undefined){ blockedPropigate(){
const premessage=this.channel.idToPrev.get(this.snowflake)?.getObject();
if(premessage?.author===this.author){
premessage.blockedPropigate();
}else{
this.generateMessage();
}
}
generateMessage(premessage:Message|undefined=undefined,ignoredblock=false){
if(!this.div) return; if(!this.div) return;
if(!premessage){ if(!premessage){
premessage=this.channel.idToPrev.get(this.snowflake)?.getObject(); premessage=this.channel.idToPrev.get(this.snowflake)?.getObject();
@ -255,7 +268,65 @@ class Message{
} }
div.innerHTML=""; div.innerHTML="";
const build = document.createElement('div'); const build = document.createElement('div');
build.classList.add("flexltr");
build.classList.add("flexltr","message");
div.classList.remove("zeroheight")
if(this.author.relationshipType===2){
if(ignoredblock){
if(premessage?.author!==this.author){
const span=document.createElement("span");
span.textContent=`You have this user blocked, click to hide these messages.`;
div.append(span);
span.classList.add("blocked")
span.onclick=_=>{
const scroll=this.channel.infinite.scrollTop;
let next:Message|undefined=this;
while(next?.author===this.author){
next.generateMessage(undefined);
next=this.channel.idToNext.get(next.snowflake)?.getObject();
}
if(this.channel.infinite.scroll&&scroll){
this.channel.infinite.scroll.scrollTop=scroll;
}
}
}
}else{
div.classList.remove("topMessage");
if(premessage?.author===this.author){
div.classList.add("zeroheight")
premessage.blockedPropigate();
div.appendChild(build);
return div;
}else{
build.classList.add("blocked","topMessage")
const span=document.createElement("span");
let count=1;
let next=this.channel.idToNext.get(this.snowflake)?.getObject()
while(next?.author===this.author){
count++;
next=this.channel.idToNext.get(next.snowflake)?.getObject()
}
span.textContent=`You have this user blocked, click to see the ${count} blocked messages.`;
build.append(span);
span.onclick=_=>{
const scroll=this.channel.infinite.scrollTop;
const func=this.channel.infinite.snapBottom();
let next:Message|undefined=this;
while(next?.author===this.author){
next.generateMessage(undefined,true);
next=this.channel.idToNext.get(next.snowflake)?.getObject();
console.log("loopy")
}
if(this.channel.infinite.scroll&&scroll){
func();
this.channel.infinite.scroll.scrollTop=scroll;
}
}
div.appendChild(build);
return div;
}
}
}
if(this.message_reference){ if(this.message_reference){
const replyline=document.createElement("div"); const replyline=document.createElement("div");
const line=document.createElement("hr"); const line=document.createElement("hr");
@ -267,7 +338,6 @@ class Message{
replyline.appendChild(username); replyline.appendChild(username);
const reply=document.createElement("div"); const reply=document.createElement("div");
username.classList.add("username"); username.classList.add("username");
this.author.bind(username,this.guild);
reply.classList.add("replytext"); reply.classList.add("replytext");
replyline.appendChild(reply); replyline.appendChild(reply);
const line2=document.createElement("hr"); const line2=document.createElement("hr");
@ -276,6 +346,10 @@ class Message{
line.classList.add("startreply"); line.classList.add("startreply");
replyline.classList.add("replyflex") replyline.classList.add("replyflex")
this.channel.getmessage(this.message_reference.message_id).then(message=>{ this.channel.getmessage(this.message_reference.message_id).then(message=>{
if(message.author.relationshipType===2){
username.textContent="Blocked user";
return;
}
const author=message.author; const author=message.author;
reply.appendChild(message.content.makeHTML({stdsize:true})); reply.appendChild(message.content.makeHTML({stdsize:true}));
minipfp.src=author.getpfpsrc() minipfp.src=author.getpfpsrc()
@ -288,7 +362,6 @@ class Message{
} }
div.appendChild(replyline); div.appendChild(replyline);
} }
build.classList.add("message");
div.appendChild(build); div.appendChild(build);
if({0:true,19:true}[this.type]||this.attachments.length!==0){ if({0:true,19:true}[this.type]||this.attachments.length!==0){
const pfpRow = document.createElement('div'); const pfpRow = document.createElement('div');
@ -486,24 +559,18 @@ class Message{
} }
} }
} }
let now = new Date().toLocaleDateString();
const yesterday = new Date(now);
yesterday.setDate(new Date().getDate() - 1);
let yesterdayStr=yesterday.toLocaleDateString();
function formatTime(date:Date) {
function formatTime(date) { const datestring=date.toLocaleDateString();
const now = new Date(); const formatTime = (date:Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const sameDay = date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() &&
date.getFullYear() === now.getFullYear();
const yesterday = new Date(now); if (datestring=== now) {
yesterday.setDate(now.getDate() - 1);
const isYesterday = date.getDate() === yesterday.getDate() &&
date.getMonth() === yesterday.getMonth() &&
date.getFullYear() === yesterday.getFullYear();
const formatTime = date => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
if (sameDay) {
return `Today at ${formatTime(date)}`; return `Today at ${formatTime(date)}`;
} else if (isYesterday) { } else if (datestring===yesterdayStr) {
return `Yesterday at ${formatTime(date)}`; return `Yesterday at ${formatTime(date)}`;
} else { } else {
return `${date.toLocaleDateString()} at ${formatTime(date)}`; return `${date.toLocaleDateString()} at ${formatTime(date)}`;

View file

@ -58,10 +58,6 @@ video{
padding:.03in; padding:.03in;
} }
th {
font-weight: 400;
padding: 0;
}
.background { .background {
position: absolute; position: absolute;
@ -305,13 +301,10 @@ img {
width: 100%; width: 100%;
/* flex-grow: 1; */ /* flex-grow: 1; */
overflow-x: clip; overflow-x: clip;
transform: translateZ(0);
-webkit-transform: translateZ(0);
} }
#messages {
max-width: 100%;
/* height: 100%; */
width: 100%;
}
p { p {
margin-top: 0; margin-top: 0;
@ -334,9 +327,6 @@ p {
/* height: 100%; */ /* height: 100%; */
} }
#channels p {
text-indent: 10px;
}
.space { .space {
margin: .02in; margin: .02in;
@ -348,13 +338,6 @@ p {
height: .175in; height: .175in;
} }
.spacee {
text-indent: .15in;
margin-right: .02in;
font-size: .15in;
display: inline-block;
width: .2in;
}
#channels p2 { #channels p2 {
font-weight: bold; font-weight: bold;
@ -753,13 +736,6 @@ textarea:focus-visible,
background-color: var(--channel-hover); background-color: var(--channel-hover);
} }
.dm-container {
display: flex;
flex-direction: column;
}
.dm-container div img {
padding: 5px;
}
.messageimg { .messageimg {
cursor: pointer; cursor: pointer;
@ -799,17 +775,6 @@ textarea:focus-visible,
flex-grow: 0; flex-grow: 0;
} }
#channels-td {
padding-right: 240px;
flex-grow: 1;
display: flex;
flex-direction: column;
height: 100%;
flex-shrink: 1;
}
#userinfo { #userinfo {
position:relative; position:relative;
background-color: var(--user-info-bg); background-color: var(--user-info-bg);
@ -1109,12 +1074,6 @@ span {
.switchtable:hover{ .switchtable:hover{
background:var(--profile-info-bg); background:var(--profile-info-bg);
} }
.accountSwitcher tr:hover{
background:var(--profile-info-bg);
}
.switchtable tr{
background-color:transparent;
}
.serverURL{ .serverURL{
color: var(--pronouns); color: var(--pronouns);
word-wrap: normal; word-wrap: normal;
@ -1540,6 +1499,8 @@ span {
} }
.scroller{ .scroller{
padding-bottom: .2in; padding-bottom: .2in;
flex-shrink: 0;
flex-grow: 0;
} }
.suberror{ .suberror{
animation: goout 6s forwards; animation: goout 6s forwards;
@ -1955,3 +1916,14 @@ form div{
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
} }
.blocked{
padding-left: .45in;
cursor: pointer;
font-weight: bold;
}
.zeroheight{
height: 0px;
flex-shrink:1;
display: block;
padding: 0;
}

View file

@ -14,6 +14,8 @@ class User{
snowflake:SnowFlake<User>; snowflake:SnowFlake<User>;
avatar:string; avatar:string;
username:string; username:string;
nickname:string|null=null;
relationshipType:0|1|2|3|4=0;
bio:MarkDown; bio:MarkDown;
discriminator:string; discriminator:string;
pronouns:string; pronouns:string;
@ -66,7 +68,7 @@ class User{
get id(){ get id(){
return this.snowflake.id; return this.snowflake.id;
} }
static contextmenu:Contextmenu=new Contextmenu("User Menu"); static contextmenu=new Contextmenu<User,Member|undefined>("User Menu");
static setUpContextMenu(){ static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(this:User){ this.contextmenu.addbutton("Copy user id",function(this:User){
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
@ -78,7 +80,26 @@ class User{
headers: this.localuser.headers headers: this.localuser.headers
}); });
}); });
this.contextmenu.addbutton("Block user",function(this:User){
this.block();
},null,function(){
return this.relationshipType!==2
});
this.contextmenu.addbutton("Unblock user",function(this:User){
this.unblock();
},null,function(){
return this.relationshipType===2
});
this.contextmenu.addbutton("Friend request",function(this:User){
fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{
method:"PUT",
headers:this.owner.headers,
body:JSON.stringify({
type:1
})
})
});
} }
static clear(){ static clear(){
this.userids={}; this.userids={};
@ -150,6 +171,7 @@ class User{
} }
buildpfp(){ buildpfp(){
const pfp=document.createElement('img'); const pfp=document.createElement('img');
pfp.loading="lazy";
pfp.src=this.getpfpsrc(); pfp.src=this.getpfpsrc();
pfp.classList.add("pfp"); pfp.classList.add("pfp");
pfp.classList.add("userid:"+this.id); pfp.classList.add("userid:"+this.id);
@ -185,6 +207,7 @@ class User{
bind(html:HTMLElement,guild:Guild|null=null,error=true){ bind(html:HTMLElement,guild:Guild|null=null,error=true){
if(guild&&guild.id!=="@me"){ if(guild&&guild.id!=="@me"){
Member.resolveMember(this,guild).then(_=>{ Member.resolveMember(this,guild).then(_=>{
User.contextmenu.bindContextmenu(html,this,_);
if(_===undefined&&error){ if(_===undefined&&error){
const error=document.createElement("span"); const error=document.createElement("span");
error.textContent="!"; error.textContent="!";
@ -204,8 +227,6 @@ class User{
}else{ }else{
this.profileclick(html); this.profileclick(html);
} }
User.contextmenu.bind(html,this);
} }
static async resolve(id:string,localuser:Localuser){ static async resolve(id:string,localuser:Localuser){
const json=await fetch(localuser.info.api.toString()+"/users/"+id+"/profile", const json=await fetch(localuser.info.api.toString()+"/users/"+id+"/profile",
@ -222,6 +243,35 @@ class User{
(thing as HTMLImageElement).src=src; (thing as HTMLImageElement).src=src;
} }
} }
block(){
fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{
method:"PUT",
headers:this.owner.headers,
body:JSON.stringify({
type:2
})
})
this.relationshipType=2;
const channel=this.localuser.channelfocus;
if(channel){
for(const thing of channel.messages){
thing[1].generateMessage();
}
}
}
unblock(){
fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{
method:"DELETE",
headers:this.owner.headers,
})
this.relationshipType=0;
const channel=this.localuser.channelfocus;
if(channel){
for(const thing of channel.messages){
thing[1].generateMessage();
}
}
}
getpfpsrc(){ getpfpsrc(){
if(this.hypotheticalpfp){ if(this.hypotheticalpfp){
return this.avatar; return this.avatar;