message pins and better welcome messages

This commit is contained in:
MathMan05 2025-04-09 13:58:13 -05:00
parent 71ee9d3f36
commit 1ade94470a
11 changed files with 261 additions and 21 deletions

View file

@ -330,6 +330,7 @@ class Channel extends SnowFlake {
this.readbottom.bind(this),
);
}
last_pin_timestamp?: string;
constructor(json: channeljson | -1, owner: Guild, id: string = json === -1 ? "" : json.id) {
super(id);
if (json === -1) {
@ -401,6 +402,61 @@ class Channel extends SnowFlake {
get info() {
return this.owner.info;
}
pinnedMessages?: Message[];
async pinnedClick(rect: DOMRect) {
const div = document.createElement("div");
div.classList.add("flexttb", "pinnedMessages");
div.style.top = rect.bottom + 20 + "px";
div.style.right = window.innerWidth - rect.right + "px";
document.body.append(div);
Contextmenu.keepOnScreen(div);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
this.last_pin_timestamp = this.lastpin;
const l = (e: MouseEvent) => {
if (e.target instanceof HTMLElement && div.contains(e.target)) {
return;
}
div.remove();
document.removeEventListener("click", l);
};
document.addEventListener("mouseup", l);
if (!this.pinnedMessages) {
const pinnedM = (await (
await fetch(`${this.info.api}/channels/${this.id}/pins`, {headers: this.headers})
).json()) as messagejson[];
this.pinnedMessages = pinnedM.map((_) => {
if (this.messages.has(_.id)) {
return this.messages.get(_.id) as Message;
} else {
return new Message(_, this);
}
});
}
const pinnedM = document.getElementById("pinnedMDiv");
if (pinnedM) {
pinnedM.classList.remove("unreadPin");
}
if (this.pinnedMessages.length === 0) {
const b = document.createElement("b");
b.textContent = I18n.noPins();
div.append(b);
return;
}
div.append(
...this.pinnedMessages.map((_) => {
const html = _.buildhtml(undefined, true);
html.style.cursor = "pointer";
html.onclick = async () => {
div.remove();
await this.focus(_.id);
};
Message.contextmenu.bindContextmenu(html, _);
return html;
}),
);
}
readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]) {
const next = this.messages.get(this.idToNext.get(this.lastreadmessageid as string) as string);
this.lastreadmessageid = json.last_message_id;
@ -927,7 +983,19 @@ class Channel extends SnowFlake {
html.classList.add("messagecontainer");
messages.append(html);
}
unreadPins() {
if (!this.last_pin_timestamp && !this.lastpin) return false;
return this.last_pin_timestamp !== this.lastpin;
}
async getHTML(addstate = true) {
const pinnedM = document.getElementById("pinnedMDiv");
if (pinnedM) {
if (this.unreadPins()) {
pinnedM.classList.add("unreadPin");
} else {
pinnedM.classList.remove("unreadPin");
}
}
const ghostMessages = document.getElementById("ghostMessages") as HTMLElement;
ghostMessages.innerHTML = "";
for (const thing of this.fakeMessages) {

View file

@ -429,6 +429,14 @@ class Group extends Channel {
return div;
}
async getHTML(addstate = true) {
const pinnedM = document.getElementById("pinnedMDiv");
if (pinnedM) {
if (this.unreadPins()) {
pinnedM.classList.add("unreadPin");
} else {
pinnedM.classList.remove("unreadPin");
}
}
const ghostMessages = document.getElementById("ghostMessages") as HTMLElement;
ghostMessages.innerHTML = "";
for (const thing of this.fakeMessages) {

View file

@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"><path style="fill:#fd0000;fill-opacity:1;stroke:#fe0000;stroke-width:38.1973;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" d="m287 26 196 196-44-24-192 193-20 87L31 283l88-21L311 70Z"/><path style="fill:#fd0000;stroke:#fe0000;stroke-width:42.3359;stroke-linecap:round;stroke-linejoin:round" d="m83 400 19 20-76 57z"/></svg>

After

Width:  |  Height:  |  Size: 403 B

View file

@ -74,6 +74,10 @@
<span id="channelname">Channel name</span>
<span id="channelTopic" class="ellipsis" hidden>Channel topic</span>
</span>
<div class="spaceElm"></div>
<div id="pinnedMDiv">
<span class="svgicon svg-pin" id="pinnedM"></span>
</div>
<div contenteditable="true" class="searchBox" id="searchBox"></div>
<label for="memberlisttoggle" id="memberlisttoggleicon">
<span class="svgicon svg-friends"></span>

View file

@ -251,6 +251,12 @@ import {I18n} from "./i18n.js";
}
}
});
const pinnedM = document.getElementById("pinnedM") as HTMLElement;
pinnedM.onclick = (e) => {
thisUser.pinnedClick(pinnedM.getBoundingClientRect());
e.preventDefault();
e.stopImmediatePropagation();
};
(document.getElementById("upload") as HTMLElement).onclick = () => {
const input = document.createElement("input");
input.type = "file";

View file

@ -617,6 +617,15 @@ type wsjson =
t: "GUILD_UPDATE";
d: extendedProperties;
s: number;
}
| {
op: 0;
t: "CHANNEL_PINS_UPDATE";
d: {
channel_id: string;
guild_id: string;
};
s: number;
};
type memberChunk = {

View file

@ -606,6 +606,17 @@ class Localuser {
}
}
break;
case "CHANNEL_PINS_UPDATE":
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if (!channel) break;
delete channel.pinnedMessages;
channel.lastpin = new Date() + "";
const pinnedM = document.getElementById("pinnedMDiv");
if (pinnedM) {
pinnedM.classList.add("unreadPin");
}
break;
case "CHANNEL_UPDATE":
if (this.initialized) {
this.updateChannel(temp.d);
@ -2252,6 +2263,10 @@ class Localuser {
this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre);
};
}
async pinnedClick(rect: DOMRect) {
if (!this.channelfocus) return;
await this.channelfocus.pinnedClick(rect);
}
async makeGifBox(rect: DOMRect) {
interface fullgif {
id: string;

View file

@ -16,7 +16,7 @@ import {Hover} from "./hover.js";
import {Dialog} from "./settings.js";
class Message extends SnowFlake {
static contextmenu = new Contextmenu<Message, undefined>("message menu");
static contextmenu = new Contextmenu<Message, void>("message menu");
owner: Channel;
headers: Localuser["headers"];
embeds!: Embed[];
@ -50,6 +50,7 @@ class Message extends SnowFlake {
div: HTMLDivElement | undefined;
member: Member | undefined;
reactions!: messagejson["reactions"];
pinned!: boolean;
static setup() {
this.del = new Promise((_) => {
this.resolve = _;
@ -96,6 +97,9 @@ class Message extends SnowFlake {
icon: {
css: "svg-emoji",
},
visable: function () {
return this.channel.hasPermission("ADD_REACTIONS");
},
},
);
@ -111,6 +115,48 @@ class Message extends SnowFlake {
},
},
);
Message.contextmenu.addButton(
() => I18n.pinMessage(),
async function (this: Message) {
const f = await fetch(`${this.info.api}/channels/${this.channel.id}/pins/${this.id}`, {
method: "PUT",
headers: this.headers,
});
if (!f.ok) alert(I18n.unableToPin());
},
{
icon: {
css: "svg-pin",
},
visable: function () {
if (this.pinned) return false;
if (this.channel.guild.id === "@me") return true;
return this.channel.hasPermission("MANAGE_MESSAGES");
},
},
);
Message.contextmenu.addButton(
() => I18n.unpinMessage(),
async function (this: Message) {
const f = await fetch(`${this.info.api}/channels/${this.channel.id}/pins/${this.id}`, {
method: "DELETE",
headers: this.headers,
});
if (!f.ok) alert(I18n.unableToPin());
},
{
icon: {
css: "svg-pin",
},
visable: function () {
if (!this.pinned) return false;
if (this.channel.guild.id === "@me") return true;
return this.channel.hasPermission("MANAGE_MESSAGES");
},
},
);
Message.contextmenu.addButton(
() => I18n.getTranslation("copymessageid"),
function (this: Message) {
@ -201,17 +247,20 @@ class Message extends SnowFlake {
this.embeds[thing] = new Embed(messagejson.embeds[thing], this);
}
continue;
} else if (thing === "author") {
continue;
}
(this as any)[thing] = (messagejson as any)[thing];
}
if (messagejson.reactions?.length) {
console.log(messagejson.reactions, ":3");
}
console.log(messagejson.webhook);
if (messagejson.webhook) {
messagejson.author.webhook = messagejson.webhook;
}
this.author = new User(messagejson.author, this.localuser, false);
if (messagejson.author.id) {
this.author = new User(messagejson.author, this.localuser, false);
}
for (const thing in messagejson.mentions) {
this.mentions[thing] = new User(messagejson.mentions[thing], this.localuser);
}
@ -504,7 +553,7 @@ class Message extends SnowFlake {
}
}
}
if (this.message_reference) {
if (this.message_reference && this.type !== 6) {
const replyline = document.createElement("div");
const minipfp = document.createElement("img");
@ -688,11 +737,15 @@ class Message extends SnowFlake {
}
//
} else if (this.type === 7) {
const messages = I18n.welcomeMessages("|||").split("\n");
const message = messages[Number(BigInt(this.id) % BigInt(messages.length))];
const [first, second] = message.split("|||");
const text = document.createElement("div");
build.appendChild(text);
const messaged = document.createElement("span");
messaged.textContent = "welcome: ";
text.appendChild(messaged);
const firstspan = document.createElement("span");
firstspan.textContent = first;
text.appendChild(firstspan);
const username = document.createElement("span");
username.textContent = this.author.username;
@ -701,6 +754,37 @@ class Message extends SnowFlake {
text.appendChild(username);
username.classList.add("username");
const secondspan = document.createElement("span");
secondspan.textContent = second;
text.appendChild(secondspan);
const time = document.createElement("span");
time.textContent = " " + formatTime(new Date(this.timestamp));
time.classList.add("timestamp");
text.append(time);
div.classList.add("topMessage");
} else if (this.type === 6) {
const text = document.createElement("div");
build.appendChild(text);
const m = I18n.message.pin("||").split("||");
if (m.length === 2) text.append(m.shift() as string);
const username = document.createElement("span");
username.textContent = this.author.username;
//this.author.profileclick(username);
this.author.bind(username, this.guild);
text.appendChild(username);
username.classList.add("username");
const afterText = document.createElement("span");
afterText.textContent = m[0];
afterText.onclick = (_) => {
this.channel.infinite.focus(this.message_reference.message_id);
};
afterText.classList.add("pinText");
text.append(afterText);
const time = document.createElement("span");
time.textContent = " " + formatTime(new Date(this.timestamp));
time.classList.add("timestamp");

View file

@ -57,6 +57,24 @@ body {
display: flex;
flex-direction: column;
}
.pinnedMessages {
position: absolute;
background: var(--secondary-bg);
width: 3.5in;
padding: 8px;
border-radius: 6px;
box-shadow: 1px 1px 10px black;
max-height: 60vh;
overflow-y: auto;
min-height: 1in;
b {
width: 100%;
height: 1in;
display: flex;
align-items: center;
justify-content: center;
}
}
.searchOptions {
padding: 0.05in 0.15in;
border-radius: 0.1in;
@ -337,6 +355,10 @@ textarea {
.svg-spoiler {
mask: url(/icons/spoiler.svg);
}
.svg-pin {
mask: url(/icons/pin.svg);
mask-size: cover;
}
.svg-unspoiler {
mask: url(/icons/unspoiler.svg);
}
@ -436,6 +458,24 @@ textarea {
aspect-ratio: 1/1;
flex-shrink: 0;
}
#pinnedM {
width: 0.25in;
height: 0.25in;
cursor: pointer;
}
.unreadPin {
position: relative;
}
.unreadPin::after {
width: 0.1in;
height: 0.1in;
content: "";
background: var(--red);
position: absolute;
right: 0px;
bottom: -5px;
border-radius: 1in;
}
#emojiTB {
width: 0.2in;
height: 0.2in;
@ -1197,10 +1237,13 @@ span.instanceStatus {
width: 3in;
margin: 0 0.1in;
overflow: hidden;
margin-left: auto;
flex-shrink: 0;
transition: width 0.2s;
}
.spaceElm {
margin: auto;
}
.outerTypeBox > span::before {
content: "\feff";
}
@ -1373,6 +1416,12 @@ span.instanceStatus {
word-break: break-all;
cursor: pointer;
}
.pinText {
cursor: pointer;
}
.pinText:hover {
text-decoration: underline;
}
.username:hover {
text-decoration: underline;
}
@ -2709,13 +2758,7 @@ fieldset input[type="radio"] {
border-radius: 4px;
}
cursor: pointer;
position: absolute;
overflow: hidden;
}

View file

@ -6,7 +6,8 @@
"comment": "Don't know how often I'll update this top part lol"
},
"readableName": "English",
"pinMessage": "Pin Message",
"unableToPin": "Unable to pin message",
"reply": "Reply",
"copyrawtext": "Copy raw text",
"copymessageid": "Copy message id",
@ -289,6 +290,7 @@
"INVITES_DISABLED": "Invite only",
"DISCOVERABLE": "Discovery"
},
"welcomeMessages": "Welcome $1! Nice to see ya!\nHello, nice to meat you $1!\nWelcome, hope you brought pizza $1!",
"role": {
"displaySettings": "Display settings",
"name": "Role name:",
@ -396,6 +398,8 @@
"genericType": "Generic",
"copy": "Copy"
},
"unpinMessage": "Unpin Message",
"noPins": "There seems to be nothing here... yet",
"message": {
"reactionAdd": "Add reaction",
"delete": "Delete message",
@ -403,7 +407,8 @@
"edited": "(edited)",
"deleted": "Deleted message",
"attached": "Sent an attachment",
"retry": "Resend errored message"
"retry": "Resend errored message",
"pin": "$1 has pined a message"
},
"instanceStats": {
"name": "Instance stats: $1",

View file

@ -3,11 +3,7 @@
"last-updated": "2024/11/4",
"locale": "en",
"comment": "Don't know how often I'll update this top part lol",
"authors": [
"MathMan05",
"McDutchie",
"Vl1"
]
"authors": ["MathMan05", "McDutchie", "Vl1"]
},
"readableName": "{{doc-important|This should be the name of the language you are translating into, in that language. Please DO NOT translate this into your languages word for “English”!}}",
"typing": "$1 is the number of people typing and $2 is the names of the people typing separated by commas",
@ -23,6 +19,7 @@
"guild": {
"disoveryTitle": "$1 is the number of guilds discovered"
},
"welcomeMessages": "These are welcome messages, they are meant to be silly, you do not need to directly translated them, there may even be a different count of messages, but only have the username once per message.",
"localuser": {
"updateSettings": "Title of a page that allows you to adjust the update settings",
"description": "This object contains strings related to the logged in user, which is mostly the settings for the user",