diff --git a/.dist/channel.js b/.dist/channel.js index 7805fa4..460703e 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -58,14 +58,14 @@ class Channel { this.contextmenu.addbutton("Delete channel", function () { console.log(this); this.deleteChannel(); - }, null, _ => { console.log(_); return _.isAdmin(); }); + }, null, function () { return this.isAdmin(); }); this.contextmenu.addbutton("Edit channel", function () { this.editChannel(); - }, null, _ => { return _.isAdmin(); }); + }, null, function () { return this.isAdmin(); }); this.contextmenu.addbutton("Make invite", function () { this.createInvite(); - }, null, (_) => { - return _.hasPermission("CREATE_INSTANT_INVITE") && _.type !== 4; + }, null, function () { + return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; }); /* this.contextmenu.addbutton("Test button",function(){ @@ -154,7 +154,7 @@ class Channel { } setUpInfiniteScroller() { 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 (this.idToPrev.has(snowflake)) { return this.idToPrev.get(snowflake)?.id; @@ -184,12 +184,15 @@ class Channel { const html = messgage.buildhtml(); return html; } + else { + console.error(id + " not found"); + } } catch (e) { console.error(e); } }.bind(this), async function (id) { - const message = SnowFlake.getSnowFlakeFromID(id, Message).getObject(); + const message = this.messages.get(id); try { if (message) { message.deleteDiv(); @@ -374,7 +377,7 @@ class Channel { caps.classList.add("capsflex"); decdiv.classList.add("channeleffects"); decdiv.classList.add("channel"); - Channel.contextmenu.bind(decdiv, this); + Channel.contextmenu.bindContextmenu(decdiv, this, undefined); decdiv["all"] = this; for (const channel of this.children) { childrendiv.appendChild(channel.createguildHTML(admin)); @@ -400,7 +403,7 @@ class Channel { if (this.hasunreads) { div.classList.add("cunread"); } - Channel.contextmenu.bind(div, this); + Channel.contextmenu.bindContextmenu(div, this, undefined); if (admin) { this.coatDropDiv(div); } @@ -815,12 +818,6 @@ class Channel { async grabArround(id) { 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() { /* if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){ diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index 3ec5f98..9d39a87 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -27,23 +27,23 @@ class Contextmenu { this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); return {}; } - makemenu(x, y, addinfo, obj) { + makemenu(x, y, addinfo, other) { const div = document.createElement("div"); div.classList.add("contextmenu", "flexttb"); for (const thing of this.buttons) { - if (!thing[3](addinfo)) { + if (!thing[3].bind(addinfo)(other)) { continue; } const intext = document.createElement("button"); - intext.disabled = !thing[4](); + intext.disabled = !thing[4].bind(addinfo)(other); intext.classList.add("contextbutton"); intext.textContent = thing[0]; console.log(thing); if (thing[5] === "button") { - intext.onclick = thing[1].bind(addinfo, obj); + intext.onclick = thing[1].bind(addinfo, other); } else if (thing[5] === "submenu") { - intext.onclick = thing[1].bind(addinfo); + intext.onclick = thing[1].bind(addinfo, other); } div.appendChild(intext); } @@ -58,11 +58,11 @@ class Contextmenu { Contextmenu.currentmenu = div; return this.div; } - bind(obj, addinfo = undefined) { + bindContextmenu(obj, addinfo, other) { const func = (event) => { event.preventDefault(); event.stopImmediatePropagation(); - this.makemenu(event.clientX, event.clientY, addinfo, obj); + this.makemenu(event.clientX, event.clientY, addinfo, other); }; obj.addEventListener("contextmenu", func); return func; diff --git a/.dist/guild.js b/.dist/guild.js index 6e2afc2..f8d91e6 100644 --- a/.dist/guild.js +++ b/.dist/guild.js @@ -42,10 +42,14 @@ class Guild { }); Guild.contextmenu.addbutton("Leave guild", function () { 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.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 () { console.log(this); }, null, _ => true, _ => false); @@ -271,7 +275,7 @@ class Guild { this.loadGuild(); this.loadChannel(); }; - Guild.contextmenu.bind(img, this); + Guild.contextmenu.bindContextmenu(img, this, undefined); } else { const div = document.createElement("div"); @@ -283,7 +287,7 @@ class Guild { this.loadGuild(); this.loadChannel(); }; - Guild.contextmenu.bind(div, this); + Guild.contextmenu.bindContextmenu(div, this, undefined); } return divy; } diff --git a/.dist/index.js b/.dist/index.js index b243d28..e40eb59 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -103,7 +103,7 @@ catch (e) { 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 () { if (thisuser.lookingguild) { thisuser.lookingguild.createchannels(); @@ -114,7 +114,7 @@ catch (e) { thisuser.lookingguild.createcategory(); } }, null, _ => { return thisuser.isAdmin(); }); - menu.bind(document.getElementById("channels")); + menu.bindContextmenu(document.getElementById("channels"), 0, 0); } const pasteimage = document.getElementById("pasteimage"); let replyingto = null; diff --git a/.dist/infiniteScroller.js b/.dist/infiniteScroller.js index 64104c2..a2e04ca 100644 --- a/.dist/infiniteScroller.js +++ b/.dist/infiniteScroller.js @@ -3,8 +3,9 @@ class InfiniteScroller { getHTMLFromID; destroyFromID; reachesBottom; - minDist = 3000; - maxDist = 8000; + minDist = 2000; + fillDist = 3000; + maxDist = 6000; HTMLElements = []; div; scroll; @@ -25,11 +26,21 @@ class InfiniteScroller { this.div = div; //this.interval=setInterval(this.updatestuff.bind(this,true),100); this.scroll = scroll; - this.div.addEventListener("scroll", this.watchForChange.bind(this)); - this.scroll.addEventListener("scroll", this.watchForChange.bind(this)); + this.div.addEventListener("scroll", _ => { + 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; new ResizeObserver(_ => { + this.updatestuff(); const change = oldheight - div.offsetHeight; if (change > 0 && this.scroll) { this.scroll.scrollTop += change; @@ -49,11 +60,16 @@ class InfiniteScroller { scrollBottom; scrollTop; needsupdate = true; + averageheight = 60; async updatestuff() { + this.timeout = null; if (!this.scroll) return; - this.timeout = null; 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; if (!this.scrollBottom) { if (!await this.watchForChange()) { @@ -84,120 +100,160 @@ class InfiniteScroller { const scrollBottom = this.scrollBottom; return () => { 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) return false; - let again = false; - if (this.scrollTop === 0) { - this.scrollTop = 1; - this.scroll.scrollTop = 1; - } - if (this.scrollTop < this.minDist) { - let nextid; - const firstelm = this.HTMLElements.at(0); - 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"); + try { + let again = false; + if (this.scrollTop < (already ? this.fillDist : this.minDist)) { + let nextid; + const firstelm = this.HTMLElements.at(0); + if (firstelm) { + const previd = firstelm[1]; + nextid = await this.getIDFromOffset(previd, 1); } - this.scroll.prepend(html); - this.HTMLElements.unshift([html, nextid]); - this.scrollTop += 60; + if (!nextid) { + } + 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) { - const html = this.HTMLElements.shift(); - if (html) { - again = true; - await this.destroyFromID(html[1]); - this.scrollTop -= 60; + 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() { + async watchForBottom(already = false, fragement = new DocumentFragment()) { if (!this.scroll) return false; - let again = false; - const scrollBottom = this.scrollBottom; - if (scrollBottom < this.minDist) { - let nextid; - const lastelm = this.HTMLElements.at(-1); - if (lastelm) { - const previd = lastelm[1]; - nextid = await this.getIDFromOffset(previd, -1); + try { + let again = false; + const scrollBottom = this.scrollBottom; + if (scrollBottom < (already ? this.fillDist : this.minDist)) { + let nextid; + const lastelm = this.HTMLElements.at(-1); + if (lastelm) { + 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 { - again = true; - const html = await this.getHTMLFromID(nextid); - this.scroll.appendChild(html); - this.HTMLElements.push([html, nextid]); - this.scrollBottom += 60; - if (scrollBottom < 30) { + 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; } } - ; } - 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() { - try { - if (this.currrunning) { - return false; + if (this.currrunning) { + this.watchtime = true; + if (this.changePromise) { + return await this.changePromise; } 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; } - 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); + catch (e) { + throw e; } - if (!this.currrunning) { - console.error("something really bad happened"); + finally { + this.changePromise = undefined; + setTimeout(_ => { + this.currrunning = false; + if (this.watchtime) { + this.watchForChange(); + } + }, 300); } - this.currrunning = false; - return !!changed; - } - catch (e) { - console.error(e); - } - return false; + }); + return await this.changePromise; } async focus(id, flash = true) { let element; @@ -206,6 +262,7 @@ class InfiniteScroller { element = thing[0]; } } + console.log(id, element, this.HTMLElements.length, ":3"); if (element) { if (flash) { element.scrollIntoView({ diff --git a/.dist/localuser.js b/.dist/localuser.js index 679a27d..f4f4904 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -89,6 +89,11 @@ class Localuser { const guildid = guild.snowflake; 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() { const servers = document.getElementById("servers"); diff --git a/.dist/login.js b/.dist/login.js index aa10fc8..4b06633 100644 --- a/.dist/login.js +++ b/.dist/login.js @@ -22,7 +22,6 @@ function trimswitcher() { const map = new Map(); for (const thing in json.users) { const user = json.users[thing]; - console.log(user, json.users); let wellknown = user.serverurls.wellknown; if (wellknown[wellknown.length - 1] !== "/") { wellknown += "/"; diff --git a/.dist/member.js b/.dist/member.js index b568dd2..cc99394 100644 --- a/.dist/member.js +++ b/.dist/member.js @@ -1,6 +1,5 @@ import { User } from "./user.js"; import { Role } from "./role.js"; -import { Contextmenu } from "./contextmenu.js"; import { SnowFlake } from "./snowflake.js"; class Member { static already = {}; @@ -9,18 +8,6 @@ class Member { roles = []; id; 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) { if (User.userids[memberjson.id]) { this.user = User.userids[memberjson.id]; @@ -169,12 +156,10 @@ class Member { */ html.style.color = this.getColor(); } - this.profileclick(html); - Member.contextmenu.bind(html); + //this.profileclick(html); } profileclick(html) { //to be implemented } } -Member.setUpContextMenu(); export { Member }; diff --git a/.dist/message.js b/.dist/message.js index 41ab673..b92690f 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -49,7 +49,7 @@ class Message { Message.contextmenu.addbutton("Copy raw text", function () { navigator.clipboard.writeText(this.content.rawString); }); - Message.contextmenu.addbutton("Reply", function (div) { + Message.contextmenu.addbutton("Reply", function () { this.channel.setReplying(this); }); Message.contextmenu.addbutton("Copy message id", function () { @@ -65,10 +65,14 @@ class Message { const markdown = document.getElementById("typebox")["markdown"]; markdown.txt = this.content.rawString.split(''); 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 () { this.delete(); - }, null, _ => { return _.canDelete(); }); + }, null, function () { + return this.canDelete(); + }); } constructor(messagejson, owner) { this.owner = owner; @@ -169,7 +173,7 @@ class Message { return this.owner.info; } messageevents(obj) { - const func = Message.contextmenu.bind(obj, this); + const func = Message.contextmenu.bindContextmenu(obj, this, undefined); this.div = obj; obj.classList.add("messagediv"); } @@ -245,7 +249,16 @@ class Message { } } 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) return; if (!premessage) { @@ -257,7 +270,66 @@ class Message { } div.innerHTML = ""; 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) { const replyline = document.createElement("div"); const line = document.createElement("hr"); @@ -269,7 +341,6 @@ class Message { replyline.appendChild(username); const reply = document.createElement("div"); username.classList.add("username"); - this.author.bind(username, this.guild); reply.classList.add("replytext"); replyline.appendChild(reply); const line2 = document.createElement("hr"); @@ -278,6 +349,10 @@ class Message { line.classList.add("startreply"); replyline.classList.add("replyflex"); this.channel.getmessage(this.message_reference.message_id).then(message => { + if (message.author.relationshipType === 2) { + username.textContent = "Blocked user"; + return; + } const author = message.author; reply.appendChild(message.content.makeHTML({ stdsize: true })); minipfp.src = author.getpfpsrc(); @@ -290,7 +365,6 @@ class Message { }; div.appendChild(replyline); } - build.classList.add("message"); div.appendChild(build); if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { 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) { - const now = new Date(); - const sameDay = date.getDate() === now.getDate() && - date.getMonth() === now.getMonth() && - 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) { + const datestring = date.toLocaleDateString(); + const formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + if (datestring === now) { return `Today at ${formatTime(date)}`; } - else if (isYesterday) { + else if (datestring === yesterdayStr) { return `Yesterday at ${formatTime(date)}`; } else { diff --git a/.dist/user.js b/.dist/user.js index 14b1f2d..f88fc3e 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -10,6 +10,8 @@ class User { snowflake; avatar; username; + nickname = null; + relationshipType = 0; bio; discriminator; pronouns; @@ -75,6 +77,25 @@ class User { 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() { this.userids = {}; @@ -148,6 +169,7 @@ class User { } buildpfp() { const pfp = document.createElement('img'); + pfp.loading = "lazy"; pfp.src = this.getpfpsrc(); pfp.classList.add("pfp"); pfp.classList.add("userid:" + this.id); @@ -183,6 +205,7 @@ class User { bind(html, guild = null, error = true) { if (guild && guild.id !== "@me") { Member.resolveMember(this, guild).then(_ => { + User.contextmenu.bindContextmenu(html, this, _); if (_ === undefined && error) { const error = document.createElement("span"); error.textContent = "!"; @@ -203,7 +226,6 @@ class User { else { this.profileclick(html); } - User.contextmenu.bind(html, this); } static async resolve(id, localuser) { 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; } } + 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() { if (this.hypotheticalpfp) { return this.avatar; diff --git a/webpage/channel.ts b/webpage/channel.ts index a44dfdd..da5891b 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -43,7 +43,7 @@ class Channel{ typing:number; message_notifications:number; allthewayup:boolean; - static contextmenu=new Contextmenu("channel menu"); + static contextmenu=new Contextmenu("channel menu"); replyingto:Message|null; infinite:InfiniteScroller; idToPrev:Map,SnowFlake>=new Map(); @@ -70,16 +70,16 @@ class Channel{ this.contextmenu.addbutton("Delete channel",function(this:Channel){ console.log(this) this.deleteChannel(); - },null,_=>{console.log(_);return _.isAdmin()}); + },null,function(){return this.isAdmin()}); this.contextmenu.addbutton("Edit channel",function(this:Channel){ this.editChannel(); - },null,_=>{return _.isAdmin()}); + },null,function(){return this.isAdmin()}); this.contextmenu.addbutton("Make invite",function(this:Channel){ this.createInvite(); - },null,(_:Channel)=>{ - return _.hasPermission("CREATE_INSTANT_INVITE")&&_.type!==4 + },null,function(){ + return this.hasPermission("CREATE_INSTANT_INVITE")&&this.type!==4 }); /* this.contextmenu.addbutton("Test button",function(){ @@ -170,7 +170,7 @@ class Channel{ } setUpInfiniteScroller(){ this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise{ - const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake; + const snowflake=(this.messages.get(id) as Message).snowflake; if(offset===1){ if(this.idToPrev.has(snowflake)){ return this.idToPrev.get(snowflake)?.id; @@ -196,13 +196,15 @@ class Channel{ if(messgage){ const html=messgage.buildhtml(); return html; + }else{ + console.error(id+" not found") } }catch(e){ console.error(e); } }.bind(this), async function(this:Channel,id:string){ - const message=SnowFlake.getSnowFlakeFromID(id,Message).getObject(); + const message=this.messages.get(id); try{ if(message){ message.deleteDiv(); @@ -383,7 +385,7 @@ class Channel{ decdiv.classList.add("channeleffects"); decdiv.classList.add("channel"); - Channel.contextmenu.bind(decdiv,this); + Channel.contextmenu.bindContextmenu(decdiv,this,undefined); decdiv["all"]=this; @@ -409,7 +411,7 @@ class Channel{ if(this.hasunreads){ div.classList.add("cunread"); } - Channel.contextmenu.bind(div,this); + Channel.contextmenu.bindContextmenu(div,this,undefined); if(admin){this.coatDropDiv(div);} div["all"]=this; const myhtml=document.createElement("span"); @@ -817,12 +819,6 @@ class Channel{ 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") } - buildmessage(message:Message,next:Message){ - const built=message.buildhtml(next); - if(built){ - (document.getElementById("messages") as HTMLDivElement).prepend(built); - } - } async buildmessages(){ /* if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){ diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index d76888b..e95e8c9 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -1,7 +1,7 @@ -class Contextmenu{ +class Contextmenu{ static currentmenu; 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; static setup(){ Contextmenu.currentmenu=""; @@ -19,28 +19,28 @@ class Contextmenu{ this.name=name; 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"]); 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"]) return {}; } - makemenu(x:number,y:number,addinfo:any,obj:HTMLElement){ + makemenu(x:number,y:number,addinfo:any,other:y){ const div=document.createElement("div"); div.classList.add("contextmenu","flexttb"); for(const thing of this.buttons){ - if(!thing[3](addinfo)){continue;} + if(!thing[3].bind(addinfo)(other)){continue;} const intext=document.createElement("button") - intext.disabled=!thing[4](); + intext.disabled=!thing[4].bind(addinfo)(other); intext.classList.add("contextbutton") intext.textContent=thing[0] console.log(thing) if(thing[5]==="button"){ - intext.onclick=thing[1].bind(addinfo,obj); + intext.onclick=thing[1].bind(addinfo,other); }else if(thing[5]==="submenu"){ - intext.onclick=thing[1].bind(addinfo); + intext.onclick=thing[1].bind(addinfo,other); } div.appendChild(intext); @@ -56,11 +56,11 @@ class Contextmenu{ Contextmenu.currentmenu=div; return this.div; } - bind(obj:HTMLElement,addinfo:any=undefined){ + bindContextmenu(obj:HTMLElement,addinfo:x,other:y){ const func=(event) => { event.preventDefault(); event.stopImmediatePropagation(); - this.makemenu(event.clientX,event.clientY,addinfo,obj) + this.makemenu(event.clientX,event.clientY,addinfo,other); } obj.addEventListener("contextmenu", func); return func; diff --git a/webpage/guild.ts b/webpage/guild.ts index 296091c..1d02020 100644 --- a/webpage/guild.ts +++ b/webpage/guild.ts @@ -30,7 +30,7 @@ class Guild{ get id(){ return this.snowflake.id; } - static contextmenu=new Contextmenu("guild menu"); + static contextmenu=new Contextmenu("guild menu"); static setupcontextmenu(){ Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){ console.log(this) @@ -49,11 +49,15 @@ class Guild{ Guild.contextmenu.addbutton("Leave guild",function(this:Guild){ 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){ 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){ console.log(this); @@ -282,7 +286,7 @@ class Guild{ this.loadGuild(); this.loadChannel(); } - Guild.contextmenu.bind(img,this); + Guild.contextmenu.bindContextmenu(img,this,undefined); }else{ const div=document.createElement("div"); 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.loadChannel(); } - Guild.contextmenu.bind(div,this) + Guild.contextmenu.bindContextmenu(div,this,undefined) } return divy; } diff --git a/webpage/index.ts b/webpage/index.ts index 4549167..9427d79 100644 --- a/webpage/index.ts +++ b/webpage/index.ts @@ -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(){ if(thisuser.lookingguild){ thisuser.lookingguild.createchannels(); @@ -126,7 +126,7 @@ try{ thisuser.lookingguild.createcategory(); } },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; diff --git a/webpage/infiniteScroller.ts b/webpage/infiniteScroller.ts index 87911a2..e52358f 100644 --- a/webpage/infiniteScroller.ts +++ b/webpage/infiniteScroller.ts @@ -3,8 +3,9 @@ class InfiniteScroller{ readonly getHTMLFromID:(ID:string)=>Promise; readonly destroyFromID:(ID:string)=>Promise; readonly reachesBottom:()=>void; - private readonly minDist=3000; - private readonly maxDist=8000; + private readonly minDist=2000; + private readonly fillDist=3000; + private readonly maxDist=6000; HTMLElements:[HTMLElement,string][]=[]; div:HTMLDivElement|null; scroll:HTMLDivElement|null; @@ -26,11 +27,21 @@ class InfiniteScroller{ //this.interval=setInterval(this.updatestuff.bind(this,true),100); this.scroll=scroll; - this.div.addEventListener("scroll",this.watchForChange.bind(this)); - this.scroll.addEventListener("scroll",this.watchForChange.bind(this)); + this.div.addEventListener("scroll",_=>{ + 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; new ResizeObserver(_=>{ + this.updatestuff(); const change=oldheight-div.offsetHeight; if(change>0&&this.scroll){ this.scroll.scrollTop+=change; @@ -51,10 +62,15 @@ class InfiniteScroller{ scrollBottom:number; scrollTop:number; needsupdate=true; + averageheight:number=60; async updatestuff(){ - if(!this.scroll) return; this.timeout=null; + if(!this.scroll) return; 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; if(!this.scrollBottom){ if(!await this.watchForChange()){ @@ -84,124 +100,166 @@ class InfiniteScroller{ const scrollBottom=this.scrollBottom; return ()=>{ if(this.scroll&&scrollBottom<30){ - this.scroll.scrollTop=this.scroll.scrollHeight; + this.scroll.scrollTop=this.scroll.scrollHeight+20; } } } - private async watchForTop():Promise{ + private async watchForTop(already=false,fragement=new DocumentFragment()):Promise{ if(!this.scroll) return false; - let again=false; - if(this.scrollTop===0){ - this.scrollTop=1; - this.scroll.scrollTop=1; - } - if(this.scrollTopthis.maxDist){ - const html=this.HTMLElements.shift(); - if(html){ - again=true; - await this.destroyFromID(html[1]); - this.scrollTop-=60; + if(!nextid){ + + }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; + }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{ + async watchForBottom(already=false,fragement=new DocumentFragment()):Promise{ if(!this.scroll) return false; - let again=false; - const scrollBottom = this.scrollBottom; - if(scrollBottomthis.maxDist){ + + + const html=this.HTMLElements.pop(); + if(html){ + await this.destroyFromID(html[1]); + this.scrollBottom-=this.averageheight; + 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; } - }; - } - 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|undefined; async watchForChange():Promise{ - - try{ if(this.currrunning){ - return false; + this.watchtime=true; + if(this.changePromise){ + return await this.changePromise; + }else{ + return true; + } }else{ + this.watchtime=false; this.currrunning=true; + } - if(!this.div){this.currrunning=false;return false} - const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[]; - 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")} - this.currrunning=false; - return !!changed; - }catch(e){ - console.error(e); - } - return false; + this.changePromise=new Promise(async res=>{ + try{ + + try{ + if(!this.div){res(false);return false} + const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[]; + 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; + }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){ + let element:HTMLElement|undefined; for(const thing of this.HTMLElements){ if(thing[1]===id){ element=thing[0]; } } + console.log(id,element,this.HTMLElements.length,":3"); if(element){ if(flash){ diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index 1cbc190..a9ca7b9 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -44,7 +44,12 @@ type readyjson={ view_nsfw_guilds: boolean }; guilds:guildjson[]; - relationships:any[]; + relationships:{ + id:string, + type:0|1|2|3|4, + nickname:string|null, + user:userjson + }[]; read_state:{ entries:{ id: string, diff --git a/webpage/localuser.ts b/webpage/localuser.ts index c2b524f..f73c378 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -95,6 +95,11 @@ class Localuser{ const guildid=guild.snowflake; (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{ const servers=document.getElementById("servers") as HTMLDivElement; diff --git a/webpage/login.ts b/webpage/login.ts index 45d3d56..e5b960a 100644 --- a/webpage/login.ts +++ b/webpage/login.ts @@ -23,7 +23,6 @@ function trimswitcher(){ const map=new Map(); for(const thing in json.users){ const user=json.users[thing]; - console.log(user,json.users); let wellknown=user.serverurls.wellknown; if(wellknown[wellknown.length-1]!=="/"){ wellknown+="/"; diff --git a/webpage/member.ts b/webpage/member.ts index 1e4166d..0fd9215 100644 --- a/webpage/member.ts +++ b/webpage/member.ts @@ -1,7 +1,6 @@ import {User} from "./user.js"; import {Role} from "./role.js"; import {Guild} from "./guild.js"; -import { Contextmenu } from "./contextmenu.js"; import { SnowFlake } from "./snowflake.js"; import { memberjson, presencejson, userjson } from "./jsontypes.js"; @@ -12,19 +11,6 @@ class Member{ roles:Role[]=[]; id: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){ if(User.userids[memberjson.id]){ this.user=User.userids[memberjson.id]; @@ -39,6 +25,7 @@ class Member{ if(thing==="owner"){continue} if(thing==="roles"){ for(const strrole of memberjson["roles"]){ + const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject(); this.roles.push(role); } @@ -160,12 +147,10 @@ class Member{ html.style.color=this.getColor(); } - this.profileclick(html); - Member.contextmenu.bind(html); + //this.profileclick(html); } profileclick(html:HTMLElement){ //to be implemented } } -Member.setUpContextMenu(); export {Member}; diff --git a/webpage/message.ts b/webpage/message.ts index a9b6d84..8d7cf87 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -12,7 +12,7 @@ import { memberjson, messagejson } from "./jsontypes.js"; import {Emoji} from "./emoji.js"; class Message{ - static contextmenu=new Contextmenu("message menu"); + static contextmenu=new Contextmenu("message menu"); owner:Channel; headers:Localuser["headers"]; embeds:Embed[]; @@ -54,7 +54,7 @@ class Message{ Message.contextmenu.addbutton("Copy raw text",function(this:Message){ 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); }); 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; markdown.txt=this.content.rawString.split(''); 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){ this.delete(); - },null,_=>{return _.canDelete()}) + },null,function(){ + return this.canDelete() + + }) } constructor(messagejson:messagejson,owner:Channel){ this.owner=owner; @@ -172,7 +177,7 @@ class Message{ return this.owner.info; } messageevents(obj:HTMLDivElement){ - const func=Message.contextmenu.bind(obj,this); + const func=Message.contextmenu.bindContextmenu(obj,this,undefined); this.div=obj; obj.classList.add("messagediv"); } @@ -244,7 +249,15 @@ class Message{ } } reactdiv:WeakRef; - 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(!premessage){ premessage=this.channel.idToPrev.get(this.snowflake)?.getObject(); @@ -255,7 +268,65 @@ class Message{ } div.innerHTML=""; 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){ const replyline=document.createElement("div"); const line=document.createElement("hr"); @@ -267,7 +338,6 @@ class Message{ replyline.appendChild(username); const reply=document.createElement("div"); username.classList.add("username"); - this.author.bind(username,this.guild); reply.classList.add("replytext"); replyline.appendChild(reply); const line2=document.createElement("hr"); @@ -276,6 +346,10 @@ class Message{ line.classList.add("startreply"); replyline.classList.add("replyflex") this.channel.getmessage(this.message_reference.message_id).then(message=>{ + if(message.author.relationshipType===2){ + username.textContent="Blocked user"; + return; + } const author=message.author; reply.appendChild(message.content.makeHTML({stdsize:true})); minipfp.src=author.getpfpsrc() @@ -288,7 +362,6 @@ class Message{ } div.appendChild(replyline); } - build.classList.add("message"); div.appendChild(build); if({0:true,19:true}[this.type]||this.attachments.length!==0){ 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 now = new Date(); - const sameDay = date.getDate() === now.getDate() && - date.getMonth() === now.getMonth() && - date.getFullYear() === now.getFullYear(); + const datestring=date.toLocaleDateString(); + const formatTime = (date:Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - 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) { + if (datestring=== now) { return `Today at ${formatTime(date)}`; - } else if (isYesterday) { + } else if (datestring===yesterdayStr) { return `Yesterday at ${formatTime(date)}`; } else { return `${date.toLocaleDateString()} at ${formatTime(date)}`; diff --git a/webpage/style.css b/webpage/style.css index 668794c..fcce055 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -58,10 +58,6 @@ video{ padding:.03in; } -th { - font-weight: 400; - padding: 0; -} .background { position: absolute; @@ -305,13 +301,10 @@ img { width: 100%; /* flex-grow: 1; */ overflow-x: clip; + transform: translateZ(0); + -webkit-transform: translateZ(0); } -#messages { - max-width: 100%; - /* height: 100%; */ - width: 100%; -} p { margin-top: 0; @@ -334,9 +327,6 @@ p { /* height: 100%; */ } -#channels p { - text-indent: 10px; -} .space { margin: .02in; @@ -348,13 +338,6 @@ p { height: .175in; } -.spacee { - text-indent: .15in; - margin-right: .02in; - font-size: .15in; - display: inline-block; - width: .2in; -} #channels p2 { font-weight: bold; @@ -753,13 +736,6 @@ textarea:focus-visible, background-color: var(--channel-hover); } -.dm-container { - display: flex; - flex-direction: column; -} -.dm-container div img { - padding: 5px; -} .messageimg { cursor: pointer; @@ -799,17 +775,6 @@ textarea:focus-visible, flex-grow: 0; } -#channels-td { - padding-right: 240px; - flex-grow: 1; - display: flex; - flex-direction: column; - height: 100%; - flex-shrink: 1; -} - - - #userinfo { position:relative; background-color: var(--user-info-bg); @@ -1109,12 +1074,6 @@ span { .switchtable:hover{ background:var(--profile-info-bg); } -.accountSwitcher tr:hover{ - background:var(--profile-info-bg); -} -.switchtable tr{ - background-color:transparent; -} .serverURL{ color: var(--pronouns); word-wrap: normal; @@ -1540,6 +1499,8 @@ span { } .scroller{ padding-bottom: .2in; + flex-shrink: 0; + flex-grow: 0; } .suberror{ animation: goout 6s forwards; @@ -1955,3 +1916,14 @@ form div{ justify-content: flex-start; align-items: center; } +.blocked{ + padding-left: .45in; + cursor: pointer; + font-weight: bold; +} +.zeroheight{ + height: 0px; + flex-shrink:1; + display: block; + padding: 0; +} \ No newline at end of file diff --git a/webpage/user.ts b/webpage/user.ts index 2b36171..2273a8c 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -14,6 +14,8 @@ class User{ snowflake:SnowFlake; avatar:string; username:string; + nickname:string|null=null; + relationshipType:0|1|2|3|4=0; bio:MarkDown; discriminator:string; pronouns:string; @@ -66,7 +68,7 @@ class User{ get id(){ return this.snowflake.id; } - static contextmenu:Contextmenu=new Contextmenu("User Menu"); + static contextmenu=new Contextmenu("User Menu"); static setUpContextMenu(){ this.contextmenu.addbutton("Copy user id",function(this:User){ navigator.clipboard.writeText(this.id); @@ -78,7 +80,26 @@ class User{ 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(){ this.userids={}; @@ -150,6 +171,7 @@ class User{ } buildpfp(){ const pfp=document.createElement('img'); + pfp.loading="lazy"; pfp.src=this.getpfpsrc(); pfp.classList.add("pfp"); pfp.classList.add("userid:"+this.id); @@ -185,6 +207,7 @@ class User{ bind(html:HTMLElement,guild:Guild|null=null,error=true){ if(guild&&guild.id!=="@me"){ Member.resolveMember(this,guild).then(_=>{ + User.contextmenu.bindContextmenu(html,this,_); if(_===undefined&&error){ const error=document.createElement("span"); error.textContent="!"; @@ -204,8 +227,6 @@ class User{ }else{ this.profileclick(html); } - - User.contextmenu.bind(html,this); } static async resolve(id:string,localuser:Localuser){ const json=await fetch(localuser.info.api.toString()+"/users/"+id+"/profile", @@ -222,6 +243,35 @@ class User{ (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(){ if(this.hypotheticalpfp){ return this.avatar;