diff --git a/.dist/channel.js b/.dist/channel.js index 62367db..2aa588d 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -8,8 +8,6 @@ import { Settings, RoleList } from "./settings.js"; import { Role } from "./role.js"; import { InfiniteScroller } from "./infiniteScroller.js"; import { SnowFlake } from "./snowflake.js"; -import { Emoji } from "./emoji.js"; -new Emoji(); class Channel { editing; type; diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index 17afb51..43d17a2 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -20,7 +20,11 @@ class Contextmenu { this.buttons = []; } addbutton(text, onclick, img = null, shown = _ => true, enabled = _ => true) { - this.buttons.push([text, onclick, img, shown, enabled]); + this.buttons.push([text, onclick, img, shown, enabled, "button"]); + return {}; + } + addsubmenu(text, onclick, img = null, shown = _ => true, enabled = _ => true) { + this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); return {}; } makemenu(x, y, addinfo, obj) { @@ -38,7 +42,12 @@ class Contextmenu { intext.textContent = thing[0]; textb.appendChild(intext); console.log(thing); - intext.onclick = thing[1].bind(addinfo, obj); + if (thing[5] === "button") { + intext.onclick = thing[1].bind(addinfo, obj); + } + else if (thing[5] === "submenu") { + intext.onclick = thing[1].bind(addinfo); + } div.appendChild(textb); } if (Contextmenu.currentmenu != "") { diff --git a/.dist/emoji.js b/.dist/emoji.js index 9cdf783..60ebdea 100644 --- a/.dist/emoji.js +++ b/.dist/emoji.js @@ -1,3 +1,4 @@ +import { Contextmenu } from "./contextmenu.js"; class Emoji { static emojis; static decodeEmojiList(buffer) { @@ -62,6 +63,62 @@ class Emoji { Emoji.decodeEmojiList(e); }); } + static async emojiPicker(x, y) { + let res; + const promise = new Promise((r) => { res = r; }); + const menu = document.createElement("div"); + menu.classList.add("flextttb", "emojiPicker"); + menu.style.top = y + "px"; + menu.style.left = x + "px"; + setTimeout(() => { + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + document.body.append(menu); + Contextmenu.currentmenu = menu; + Contextmenu.keepOnScreen(menu); + }, 10); + const title = document.createElement("h2"); + title.textContent = Emoji.emojis[0].name; + title.classList.add("emojiTitle"); + menu.append(title); + console.log("menu :3"); + const selection = document.createElement("div"); + selection.classList.add("flexltr"); + console.log("menu :3"); + const body = document.createElement("div"); + body.classList.add("emojiBody"); + let i = 0; + for (const thing of Emoji.emojis) { + const select = document.createElement("div"); + select.textContent = thing.emojis[0].emoji; + select.classList.add("emojiSelect"); + selection.append(select); + const clickEvent = () => { + title.textContent = thing.name; + body.innerHTML = ""; + for (const emojit of thing.emojis) { + const emoji = document.createElement("div"); + emoji.classList.add("emojiSelect"); + emoji.textContent = emojit.emoji; + body.append(emoji); + emoji.onclick = _ => { + res(emojit.emoji); + Contextmenu.currentmenu.remove(); + }; + } + }; + select.onclick = clickEvent; + if (i === 0) { + clickEvent(); + } + i++; + } + menu.append(selection); + menu.append(body); + console.log("menu :3"); + return promise; + } } Emoji.grabEmoji(); export { Emoji }; diff --git a/.dist/localuser.js b/.dist/localuser.js index 9d1844d..a406b49 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -6,6 +6,7 @@ import { Fullscreen } from "./fullscreen.js"; import { setTheme } from "./login.js"; import { SnowFlake } from "./snowflake.js"; import { Message } from "./message.js"; +import { Member } from "./member.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); class Localuser { lastSequence = null; @@ -282,7 +283,23 @@ class Localuser { this.guilds.push(guildy); this.guildids.set(guildy.id, guildy); document.getElementById("servers").insertBefore(guildy.generateGuildIcon(), document.getElementById("bottomseparator")); + break; } + case "MESSAGE_REACTION_ADD": + if (SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)) { + const message = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject(); + const guild = SnowFlake.getSnowFlakeFromID(temp.d.guild_id, Guild).getObject(); + message.giveReaction(temp.d.emoji, new Member(temp.d.member, guild)); + } + break; + case "MESSAGE_REACTION_REMOVE": + if (SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)) { + const message = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject(); + const guild = SnowFlake.getSnowFlakeFromID(temp.d.guild_id, Guild).getObject(); + console.log("test"); + message.takeReaction(temp.d.emoji, temp.d.user_id); + } + break; } } else if (temp.op === 10) { diff --git a/.dist/message.js b/.dist/message.js index 55f2993..0ccf5b9 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -5,6 +5,8 @@ import { MarkDown } from "./markdown.js"; import { Embed } from "./embed.js"; import { File } from "./file.js"; import { SnowFlake } from "./snowflake.js"; +import { Emoji } from "./emoji.js"; +new Emoji(); class Message { static contextmenu = new Contextmenu("message menu"); owner; @@ -23,6 +25,7 @@ class Message { static resolve; div; member; + reactions; get id() { return this.snowflake.id; } @@ -46,6 +49,12 @@ class Message { Message.contextmenu.addbutton("Copy message id", function () { navigator.clipboard.writeText(this.id); }); + Message.contextmenu.addsubmenu("Add reaction", function (e) { + Emoji.emojiPicker(e.x, e.y).then(_ => { + console.log(_, ":3"); + this.reactionToggle(_); + }); + }); Message.contextmenu.addbutton("Edit", function () { this.channel.editing = this; const markdown = (document.getElementById("typebox"))["markdown"]; @@ -61,6 +70,24 @@ class Message { this.headers = this.owner.headers; this.giveData(messagejson); } + reactionToggle(emoji) { + if (typeof emoji === "string") { + let remove = false; + for (const thing of this.reactions) { + if (thing.emoji.name === emoji) { + remove = thing.me; + break; + } + } + fetch(this.info.api.toString() + "/channels/" + this.channel.id + "/messages/" + this.id + "/reactions/" + encodeURIComponent(emoji) + "/@me", { + method: remove ? "DELETE" : "PUT", + headers: this.headers, + }); + } + else { + emoji; + } + } giveData(messagejson) { for (const thing of Object.keys(messagejson)) { if (thing === "attachments") { @@ -92,6 +119,9 @@ class Message { } this[thing] = messagejson[thing]; } + if (messagejson.reactions?.length) { + console.log(messagejson.reactions, ":3"); + } this.author = new User(messagejson.author, this.localuser); for (const thing in messagejson.mentions) { this.mentions[thing] = new User(messagejson.mentions[thing], this.localuser); @@ -184,6 +214,7 @@ class Message { this.channel.lastmessage = prev.getObject(); } } + reactdiv; generateMessage(premessage = null) { if (!premessage) { premessage = this.channel.idToPrev.get(this.snowflake)?.getObject(); @@ -207,25 +238,6 @@ class Message { const reply = document.createElement("div"); username.classList.add("username"); this.author.bind(username, this.guild); - /* - Member.resolve(this.author,this.guild).then(_=>{ - if(!_) {return}; - console.log(_.error); - if(_.error){ - username.textContent+="Error"; - alert("Should've gotten here") - const error=document.createElement("span"); - error.textContent="!"; - error.classList.add("membererror"); - username.after(error); - - return; - } - username.style.color=_.getColor(); - }).catch(_=>{ - console.log(_) - }); - */ reply.classList.add("replytext"); replyline.appendChild(reply); const line2 = document.createElement("hr"); @@ -352,8 +364,76 @@ class Message { div.classList.add("topMessage"); } div["all"] = this; + const reactions = document.createElement("div"); + reactions.classList.add("flexltr", "reactiondiv"); + this.reactdiv = new WeakRef(reactions); + this.updateReactions(); + div.append(reactions); return (div); } + updateReactions() { + const reactdiv = this.reactdiv.deref(); + if (!reactdiv) + return; + reactdiv.innerHTML = ""; + for (const thing of this.reactions) { + console.log(thing, ":3"); + const reaction = document.createElement("div"); + reaction.classList.add("reaction"); + if (thing.me) { + reaction.classList.add("meReacted"); + } + const emoji = document.createElement("p"); + emoji.textContent = thing.emoji.name; + reaction.append(emoji); + const count = document.createElement("p"); + count.textContent = "" + thing.count; + count.classList.add("reactionCount"); + reaction.append(count); + reactdiv.append(reaction); + reaction.onclick = _ => { + this.reactionToggle(thing.emoji.name); + }; + } + } + giveReaction(data, member) { + for (const thing of this.reactions) { + if (thing.emoji.name === data.name) { + thing.count++; + if (member.id === this.localuser.user.id) { + thing.me = true; + this.updateReactions(); + return; + } + } + } + this.reactions.push({ + count: 1, + emoji: data, + me: member.id === this.localuser.user.id + }); + this.updateReactions(); + } + takeReaction(data, id) { + console.log("test"); + for (const i in this.reactions) { + const thing = this.reactions[i]; + console.log(thing, data); + if (thing.emoji.name === data.name) { + thing.count--; + if (thing.count === 0) { + this.reactions.splice(+i, 1); + this.updateReactions(); + return; + } + if (id === this.localuser.user.id) { + thing.me = false; + this.updateReactions(); + return; + } + } + } + } buildhtml(premessage, del = Message.del) { if (this.div) { console.error(`HTML for ${this.snowflake} already exists, aborting`); diff --git a/webpage/channel.ts b/webpage/channel.ts index 60af6fa..1bcdd83 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -11,8 +11,7 @@ import { Role } from "./role.js"; import {InfiniteScroller} from "./infiniteScroller.js"; import { SnowFlake } from "./snowflake.js"; import { channeljson, messagejson, readyjson } from "./jsontypes.js"; -import {Emoji} from "./emoji.js"; -new Emoji(); + declare global { interface NotificationOptions { image?: string diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index aaf7a3f..1598185 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -1,7 +1,7 @@ class Contextmenu{ static currentmenu; name:string; - buttons:[string,Function,string,Function,Function][]; + buttons:[string,Function,string,Function,Function,string][]; div:HTMLDivElement; static setup(){ Contextmenu.currentmenu=""; @@ -20,7 +20,11 @@ class Contextmenu{ this.buttons=[] } addbutton(text:string,onclick:Function,img=null,shown=_=>true,enabled=_=>true){ - this.buttons.push([text,onclick,img,shown,enabled]) + this.buttons.push([text,onclick,img,shown,enabled,"button"]) + return {}; + } + addsubmenu(text:string,onclick:(e:MouseEvent)=>void,img=null,shown=_=>true,enabled=_=>true){ + this.buttons.push([text,onclick,img,shown,enabled,"submenu"]) return {}; } makemenu(x:number,y:number,addinfo:any,obj:HTMLElement){ @@ -36,7 +40,12 @@ class Contextmenu{ intext.textContent=thing[0] textb.appendChild(intext) console.log(thing) - intext.onclick=thing[1].bind(addinfo,obj); + if(thing[5]==="button"){ + intext.onclick=thing[1].bind(addinfo,obj); + }else if(thing[5]==="submenu"){ + intext.onclick=thing[1].bind(addinfo); + } + div.appendChild(textb); } if(Contextmenu.currentmenu!=""){ diff --git a/webpage/emoji.ts b/webpage/emoji.ts index 9ed3c69..de68db6 100644 --- a/webpage/emoji.ts +++ b/webpage/emoji.ts @@ -1,3 +1,5 @@ +import { Contextmenu } from "./contextmenu.js"; + class Emoji{ static emojis:{ name:string, @@ -70,6 +72,65 @@ class Emoji{ Emoji.decodeEmojiList(e); }) } + static async emojiPicker(x:number,y:number):Promise{ + let res:(r:Emoji|string)=>void; + const promise=new Promise((r)=>{res=r;}) + const menu=document.createElement("div"); + menu.classList.add("flextttb", "emojiPicker") + menu.style.top=y+"px"; + menu.style.left=x+"px"; + + + setTimeout(()=>{ + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } + document.body.append(menu); + Contextmenu.currentmenu=menu; + Contextmenu.keepOnScreen(menu); + },10) + + const title=document.createElement("h2"); + title.textContent=Emoji.emojis[0].name; + title.classList.add("emojiTitle"); + menu.append(title); + console.log("menu :3"); + const selection=document.createElement("div"); + selection.classList.add("flexltr"); + console.log("menu :3"); + const body=document.createElement("div"); + body.classList.add("emojiBody"); + let i=0; + for(const thing of Emoji.emojis){ + const select=document.createElement("div"); + select.textContent=thing.emojis[0].emoji; + select.classList.add("emojiSelect"); + selection.append(select); + const clickEvent=()=>{ + title.textContent=thing.name; + body.innerHTML=""; + for(const emojit of thing.emojis){ + const emoji=document.createElement("div"); + emoji.classList.add("emojiSelect"); + emoji.textContent=emojit.emoji; + body.append(emoji); + emoji.onclick=_=>{ + res(emojit.emoji); + Contextmenu.currentmenu.remove(); + } + } + }; + select.onclick=clickEvent + if(i===0){ + clickEvent(); + } + i++; + } + menu.append(selection); + menu.append(body); + console.log("menu :3"); + return promise; + } } Emoji.grabEmoji(); export {Emoji}; diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index f98cb92..e3f3629 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -271,7 +271,13 @@ type messagejson={ mention_roles: [], //need examples to fix attachments: filejson[], embeds: embedjson[], - reactions: [], //ToDo + reactions: { + count:number, + emoji:{ + name:string + },//very likely needs expanding + me:boolean, + }[], nonce: string, pinned: boolean, type: number diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 72d25d4..8d1d611 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -8,6 +8,7 @@ import {setTheme, Specialuser} from "./login.js"; import { SnowFlake } from "./snowflake.js"; import { Message } from "./message.js"; import { channeljson, guildjson, memberjson, readyjson, userjson } from "./jsontypes.js"; +import { Member } from "./member.js"; const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]); @@ -291,7 +292,24 @@ class Localuser{ this.guilds.push(guildy); this.guildids.set(guildy.id,guildy); document.getElementById("servers").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator")); + break; } + case "MESSAGE_REACTION_ADD": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ + const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); + const guild=SnowFlake.getSnowFlakeFromID(temp.d.guild_id,Guild).getObject(); + message.giveReaction(temp.d.emoji,new Member(temp.d.member,guild)); + } + break; + case "MESSAGE_REACTION_REMOVE": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ + + const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); + const guild=SnowFlake.getSnowFlakeFromID(temp.d.guild_id,Guild).getObject(); + console.log("test"); + message.takeReaction(temp.d.emoji,temp.d.user_id); + } + break; } }else if(temp.op===10){ diff --git a/webpage/message.ts b/webpage/message.ts index 988d415..1f68f6a 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -9,6 +9,8 @@ import { Role } from "./role.js"; import {File} from "./file.js"; import { SnowFlake } from "./snowflake.js"; import { messagejson } from "./jsontypes.js"; +import {Emoji} from "./emoji.js"; +new Emoji(); class Message{ static contextmenu=new Contextmenu("message menu"); @@ -28,6 +30,7 @@ class Message{ static resolve:Function; div:HTMLDivElement; member:Member; + reactions:messagejson["reactions"]; get id(){ return this.snowflake.id; } @@ -51,6 +54,12 @@ class Message{ Message.contextmenu.addbutton("Copy message id",function(){ navigator.clipboard.writeText(this.id); }); + Message.contextmenu.addsubmenu("Add reaction",function(this:Message,e){ + Emoji.emojiPicker(e.x,e.y).then(_=>{ + console.log(_,":3") + this.reactionToggle(_); + }); + }); Message.contextmenu.addbutton("Edit",function(){ this.channel.editing=this; const markdown=(document.getElementById("typebox"))["markdown"] as MarkDown; @@ -67,6 +76,24 @@ class Message{ this.giveData(messagejson); } + reactionToggle(emoji:string|Emoji){ + + if(typeof emoji === "string"){ + let remove=false; + for(const thing of this.reactions){ + if(thing.emoji.name===emoji){ + remove=thing.me; + break; + } + } + fetch(this.info.api.toString()+ "/channels/"+this.channel.id+"/messages/"+this.id+"/reactions/"+encodeURIComponent(emoji)+"/@me",{ + method:remove?"DELETE":"PUT", + headers:this.headers, + }) + }else{ + emoji + } + } giveData(messagejson:messagejson){ for(const thing of Object.keys(messagejson)){ if(thing==="attachments"){ @@ -94,7 +121,9 @@ class Message{ } this[thing]=messagejson[thing]; } - + if(messagejson.reactions?.length){ + console.log(messagejson.reactions,":3"); + } this.author=new User(messagejson.author,this.localuser); for(const thing in messagejson.mentions){ @@ -187,6 +216,7 @@ class Message{ this.channel.lastmessage=prev.getObject(); } } + reactdiv:WeakRef; generateMessage(premessage:Message=null){ if(!premessage){ premessage=this.channel.idToPrev.get(this.snowflake)?.getObject(); @@ -210,27 +240,6 @@ class Message{ const reply=document.createElement("div"); username.classList.add("username"); this.author.bind(username,this.guild); - - /* - Member.resolve(this.author,this.guild).then(_=>{ - if(!_) {return}; - console.log(_.error); - if(_.error){ - username.textContent+="Error"; - alert("Should've gotten here") - const error=document.createElement("span"); - error.textContent="!"; - error.classList.add("membererror"); - username.after(error); - - return; - } - username.style.color=_.getColor(); - }).catch(_=>{ - console.log(_) - }); - */ - reply.classList.add("replytext"); replyline.appendChild(reply); const line2=document.createElement("hr"); @@ -360,8 +369,76 @@ class Message{ } div["all"]=this; + const reactions=document.createElement("div"); + reactions.classList.add("flexltr","reactiondiv"); + this.reactdiv=new WeakRef(reactions); + this.updateReactions(); + div.append(reactions) return(div) } + updateReactions(){ + const reactdiv=this.reactdiv.deref(); + if(!reactdiv) return; + reactdiv.innerHTML=""; + for(const thing of this.reactions){ + console.log(thing,":3") + const reaction=document.createElement("div"); + reaction.classList.add("reaction"); + if(thing.me){ + reaction.classList.add("meReacted") + } + const emoji=document.createElement("p"); + emoji.textContent=thing.emoji.name; + reaction.append(emoji); + const count=document.createElement("p"); + count.textContent=""+thing.count; + count.classList.add("reactionCount"); + reaction.append(count); + reactdiv.append(reaction); + + reaction.onclick=_=>{ + this.reactionToggle(thing.emoji.name); + } + } + } + giveReaction(data:{name:string},member:Member){ + for(const thing of this.reactions){ + if(thing.emoji.name===data.name){ + thing.count++; + if(member.id===this.localuser.user.id){ + thing.me=true; + this.updateReactions(); + return; + } + } + } + this.reactions.push({ + count:1, + emoji:data, + me:member.id===this.localuser.user.id + }); + this.updateReactions(); + } + takeReaction(data:{name:string},id:string){ + console.log("test"); + for(const i in this.reactions){ + const thing=this.reactions[i]; + console.log(thing,data); + if(thing.emoji.name===data.name){ + thing.count--; + if(thing.count===0){ + this.reactions.splice(+i,1); + this.updateReactions(); + return; + } + if(id===this.localuser.user.id){ + thing.me=false; + this.updateReactions(); + return; + } + } + } + } buildhtml(premessage:Message,del:Promise=Message.del){ if(this.div){console.error(`HTML for ${this.snowflake} already exists, aborting`);return;} //premessage??=messages.lastChild; diff --git a/webpage/style.css b/webpage/style.css index 42a328c..f3819a9 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -1233,6 +1233,7 @@ span { flex-direction: row; max-height: 100%; overflow: auto; + flex-shrink: 0; } .flexttb{ display: flex; @@ -1535,4 +1536,79 @@ form div{ .svgicon{ width:.5in; height:.5in; +} +.emojiPicker{ + position:absolute; + width:3.5in; + height:4in; + background:var(--profile-info-bg); + border-radius:.15in; + border:solid var(--black) .03in; + box-shadow:black .03in .03in .2in; + display: flex; + flex-direction: column; +} +.emojiSelect{ + font-size:.23in; + text-align: center; + cursor:pointer; + width:.4in; + height:.4in; + background:color-mix(in hsl, var(--profile-info-bg),black); + border-radius:2in; + display:flex; + flex-direction: column; + justify-content: space-evenly; + border:solid var(--black) .03in; + flex-shrink:0; +} +.emojiTitle{ + padding: .03in; + padding-left: .1in; + border-bottom: solid var(--black); + width: 100%; + box-sizing: border-box; + margin-bottom: .025in; +} +.emojiBody{ + display:flex; + flex-wrap: wrap; + flex-direction: row; + align-content: stretch; + justify-content: space-evenly; + /* height: 100%; */ + overflow-y: scroll; + box-sizing: border-box; + flex-shrink: 1; + flex-grow: 1; +} +.reactionCount{ + position:absolute; + font-size:.125in; + padding:0 .01in; + bottom:-.03in; + left:-.03in; + background: var(--channels-bg); + border:solid var(--black) .021in; + border-radius:.05in; +} +.reactiondiv{ + +} +.reaction{ + width:.25in; + position:relative; + height:.25in; + display:flex; + flex-direction: row; + align-content: stretch; + background: var(--profile-info-bg); + border-radius:.05in; + justify-content: space-evenly; + border:solid var(--black); + margin-right:.05in; + p{ + flex-grow:0; + flex-shrink:0; + } } \ No newline at end of file