From b64073fda8b92e782b843225accaf8dda82f2f08 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 29 Jul 2026 04:27:47 +0000 Subject: [PATCH 01/66] feat: make favorited stars be high contrast w.r.t to the images they're ontop of Signed-off-by: lizzie --- src/webpage/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index c446e6c..f6a5c44 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1071,10 +1071,11 @@ textarea { right: 5px; transition: top 0.2s; transition-timing-function: ease-in; + mix-blend-mode: difference; background: white; cursor: pointer; &.favorited { - background: yellow; + background: orange; } } .visually-hidden { From 85977753ad4c175b35d01bea0feb5f31d2f832e6 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 23:15:55 +0000 Subject: [PATCH 02/66] feat: fix 'duck resizing' video player bug, do not preload all videos Signed-off-by: lizzie --- src/webpage/file.ts | 1 + src/webpage/style.css | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/webpage/file.ts b/src/webpage/file.ts index f0d3138..8f91e18 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -158,6 +158,7 @@ class File { video.append(source); //source.type = this.content_type; video.controls = !temp; + video.preload = "metadata"; if (this.width) video.width = this.width; if (this.height) video.height = this.height; diff --git a/src/webpage/style.css b/src/webpage/style.css index 4070b15..ffeb39d 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3151,8 +3151,10 @@ span .quote:last-of-type .quoteline { gap: 4px; flex-wrap: wrap; video { - max-height: 288px; - max-width: 288px; + min-height: 200px; + min-width: 200px; + max-height: 25%; + max-width: 50%; } } .embed { From 70c9b7df9d8046e2e201b778beb5db4382bf9019 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 31 Jul 2026 18:22:00 +0000 Subject: [PATCH 03/66] feat: vertically align emojis, make reactions slightly bigger Signed-off-by: lizzie --- src/webpage/style.css | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index f326edd..5a9756f 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3109,6 +3109,24 @@ span .quote:last-of-type .quoteline { .mentionMD:hover::before { background: var(--mention); } + +.smallemoji { + height: auto; + width: auto; + max-height: 1.25em; + max-width: 1.25em; + vertical-align: middle; +} +.bigemoji { + width: 0.6in; + object-fit: contain; + height: 0.6in; + vertical-align: middle; +} +.bigemojiUni { + font-size: 47px; +} + .reactiondiv { padding-left: 52px; gap: 0 4px; @@ -3116,7 +3134,8 @@ span .quote:last-of-type .quoteline { user-select: none; } .reaction { - min-width: 30px; + min-width: 2em; + min-height: 2em; padding: 3px 4px; margin-top: 4px; background: var(--reaction-bg); @@ -3127,6 +3146,10 @@ span .quote:last-of-type .quoteline { align-items: center; cursor: pointer; gap: 4px; + .smallemoji { + max-width: 1.5em; + max-height: 1.55em; + } } .reaction p { line-height: 1em; @@ -3138,12 +3161,6 @@ span .quote:last-of-type .quoteline { background: var(--reaction-reacted-bg); border-color: color-mix(in srgb, var(--accent-color), transparent); } -.smallemoji { - height: auto; - width: auto; - max-height: 16px; - max-width: 16px; -} /* Message External */ .filename a { @@ -4922,14 +4939,6 @@ fieldset input[type="radio"] { transition: background-color 0.2s; padding: 0.08in; } -.bigemoji { - width: 0.6in; - object-fit: contain; - height: 0.6in; -} -.bigemojiUni { - font-size: 47px; -} .friendlyButton { padding: 0.07in; background: #00000045; From 656638a27e508035e0113d382844396b63872431 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 31 Jul 2026 21:40:42 +0000 Subject: [PATCH 04/66] feat: better poll styling (and fix white mode polls) Signed-off-by: lizzie --- src/webpage/style.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index b000b7f..f1b4fdd 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -274,7 +274,7 @@ body { flex-direction: column; } .pollBody { - background: #00000059; + background: var(--secondary-bg); padding: 10px; border-radius: 4px; margin-top: 2px; @@ -284,13 +284,12 @@ body { } } .answerArea { - --bg: #0000004a; + --bg: color-mix(in srgb, var(--accent-color) 15%, transparent); background: var(--bg); - margin-top: 3px; + margin-bottom: 6px; padding: 12px; cursor: pointer; position: relative; - input { margin-left: auto; cursor: pointer; From f7513c65a7847c9993eb7b1eb9a41cd507200925 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 31 Jul 2026 21:48:25 +0000 Subject: [PATCH 05/66] make polls use accent colour --- src/webpage/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 5f852e2..7cf9082 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1361,7 +1361,7 @@ class Message extends SnowFlake { count.textContent = I18n.poll.count("" + c, per + ""); aarea.append(count); if (per) - aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${per}%)`; + aarea.style.background = `linear-gradient(to right, var(--accent-color) ${per}%, var(--bg) ${per}%)`; } aarea.onclick = () => check.click(); pollbody.append(aarea); From 9ab6a8b378f1bff76804786348d6e3921e57fcb0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 31 Jul 2026 22:02:14 +0000 Subject: [PATCH 06/66] feat: larget 'Upload files here' box Signed-off-by: lizzie --- src/webpage/style.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index b000b7f..415c187 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1598,6 +1598,12 @@ textarea { display: none; } } +#filedroptext { + max-width: 75%; + max-height: 75vh; + padding: 8em 8em 8em 8em; + text-align: center; +} .commandError { position: absolute; top: -36px; From cab7804f43ca67131bb3025d9c56a073cf7388ce Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Fri, 31 Jul 2026 17:18:16 -0500 Subject: [PATCH 07/66] remove extra space --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 415c187..0700da8 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2458,7 +2458,7 @@ span.instanceStatus { flex: 0; } #pasteimage { - height: 30%; + max-height: 30%; padding: 12px; margin: 16px; background: var(--typebox-bg); From 4a8e9c7b1e884828a48d1e7c60bc0d1b8440da1f Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Fri, 31 Jul 2026 17:48:00 -0500 Subject: [PATCH 08/66] retry and fail message --- src/webpage/channel.ts | 27 ++++++++++++++++++++++----- translations/en.json | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index dd9c580..94a74ec 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -2791,7 +2791,15 @@ class Channel extends SnowFlake { } else { (document.getElementById("typebox") as HTMLDivElement).blur(); } - if (getMessages) await this.putmessages(); + try { + if (getMessages) await this.putmessages(); + } catch (e) { + if (e instanceof Error) { + const d = new Dialog(e.message); + d.show(); + } + return; + } await prom; if (id !== Channel.genid) { @@ -2938,11 +2946,20 @@ class Channel extends SnowFlake { if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { return; } - const j = await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100", { - headers: this.headers, - }); + let response: messagejson[] | undefined = undefined; + for (let i = 0; i < 5; i++) { + try { + const j = await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100", { + headers: this.headers, + }); - const response = (await j.json()) as messagejson[]; + response = (await j.json()) as messagejson[]; + break; + } catch { + await new Promise((res) => setTimeout(res, 1000)); + } + } + if (!response) throw new Error(I18n.messageNotLoad()); if (response.length !== 100) { this.allthewayup = true; } diff --git a/translations/en.json b/translations/en.json index 567f277..0da766a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -816,6 +816,7 @@ "userInfo": "User info", "notes":"User notes" }, + "messageNotLoad":"Messages failed to load", "profileColor": "Profile color", "pronouns": "Pronouns:", "readableName": "English", From daf613398a820d424f4c4cb15231a7e2d6eb136c Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 09:42:15 +0000 Subject: [PATCH 09/66] feat: copy group (and individual) DMs channel URL Signed-off-by: lizzie --- src/webpage/direct.ts | 10 +++++++++- translations/en.json | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 6ab38a8..cfdc3db 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -492,7 +492,15 @@ class Group extends Channel { ); this.groupcontextmenu.addSeperator(undefined, "dm"); - + this.groupcontextmenu.addButton( + I18n.DMs.copyURL(), + function (this: Group) { + navigator.clipboard.writeText(`${location.origin}/channels/@me/${this.id}`); + }, + { + group: "id", + }, + ); this.groupcontextmenu.addButton( () => I18n.DMs.copyId(), function (this: Group) { diff --git a/translations/en.json b/translations/en.json index 0da766a..11b7273 100644 --- a/translations/en.json +++ b/translations/en.json @@ -39,6 +39,7 @@ "DMs": { "add": "Add someone to this DM", "close": "Close DM", + "copyURL":"Copy DM URL", "copyId": "Copy DM id", "markRead": "Mark as read", "name": "Direct Messages" From c28ce8e675fb90f9d982b3601e27506332816ee7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 09:46:26 +0000 Subject: [PATCH 10/66] feat: make 'Mark as read' topmost option for guilds Signed-off-by: lizzie --- src/webpage/guild.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 962b479..79045c7 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -197,6 +197,14 @@ class Guild extends SnowFlake { welcomeScreen?: welcomeScreen; static readonly contextmenu = new Contextmenu("guild menu"); static setupcontextmenu() { + Guild.contextmenu.addButton( + () => I18n.guild.markRead(), + function (this: Guild) { + this.markAsRead(); + }, + ); + Guild.contextmenu.addSeperator(); + Guild.contextmenu.addButton( () => I18n.guild.makeInvite(), function (this: Guild) { @@ -211,14 +219,6 @@ class Guild extends SnowFlake { color: "blue", }, ); - Guild.contextmenu.addSeperator(); - - Guild.contextmenu.addButton( - () => I18n.guild.markRead(), - function (this: Guild) { - this.markAsRead(); - }, - ); Guild.contextmenu.addButton( () => I18n.guild.notifications(), From 8eba4f1b5973589286085ba399f20c2e4952c371 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 10:03:12 +0000 Subject: [PATCH 11/66] feat: link icon Signed-off-by: lizzie --- src/webpage/icons/link.svg | 57 ++++++++++++++++++++++++++++++++++++++ src/webpage/message.ts | 4 ++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/webpage/icons/link.svg diff --git a/src/webpage/icons/link.svg b/src/webpage/icons/link.svg new file mode 100644 index 0000000..ce5b351 --- /dev/null +++ b/src/webpage/icons/link.svg @@ -0,0 +1,57 @@ + + + + + + + + + diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 5f852e2..13b6cc4 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -193,7 +193,9 @@ class Message extends SnowFlake { ); }, { - //TODO make icon + icon: { + css: "svg-link", + }, }, ); Message.contextmenu.addButton( From 8b48a733ac2a941088ec43e043eac7f0d3daf58b Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 16:02:11 +0000 Subject: [PATCH 12/66] oops forgot le css --- src/webpage/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 0700da8..8c0b6f3 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1132,6 +1132,10 @@ textarea { mask: url(./icons/pin.svg); mask-size: cover; } +.svg-link { + mask: url(./icons/link.svg); + mask-size: cover; +} .svg-rules { mask: url(./icons/rules.svg); mask-size: cover; From 191ca7c548c2cedcabf415c7e137bd475cc549f5 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 16:03:27 +0000 Subject: [PATCH 13/66] feat: bound guild descs so they dont overflow Signed-off-by: lizzie --- src/webpage/emoji.ts | 2 ++ src/webpage/style.css | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 13cde44..13b12ca 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -157,9 +157,11 @@ class Emoji { guildText.classList.add("flexttb", "guildEmojiText"); const guildName = document.createElement("span"); + guildName.id = "guildName"; guildName.textContent = lookup.guild.name; const guildDesc = document.createElement("span"); + guildDesc.id = "guildDesc"; const discoverable = lookup.guild.features.find((_) => _ === "DISCOVERABLE"); if (discoverable) { if (lookup.guild.description) { diff --git a/src/webpage/style.css b/src/webpage/style.css index 0700da8..d10d13b 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5196,6 +5196,13 @@ img.error::after { .guildEmojiText { display: flex; justify-content: center; + word-break: break-all; + #guildDesc { + color: var(--secondary-text-soft); + font-size: 0.9em; + max-height: 15em; + overflow-y: scroll; + } } .optionElement:has(.friendGroupSelect) { From 112bd2561c9e0dc2bb2f9faf7e06bd9eb53f2591 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 16:58:24 +0000 Subject: [PATCH 14/66] feat: align attachment preview horz/vert Signed-off-by: lizzie --- src/webpage/style.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 0700da8..76236a4 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2483,13 +2483,14 @@ span.instanceStatus { border-radius: 8px; overflow: hidden; height: 192px; + display: flex; + align-items: center; + justify-content: center; .unknownfile { width: 100%; height: 100%; display: flex; flex-direction: column; - align-items: center; - justify-content: center; } } .messageimgdiv { From 4254e6fefcf608f6190e92904f46f2f9c39948ba Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 18:23:41 +0000 Subject: [PATCH 15/66] feat: line break so i can type long words without remorse Signed-off-by: lizzie --- src/webpage/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 0700da8..c1cde02 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2557,6 +2557,7 @@ span.instanceStatus { flex-shrink: 1; text-wrap: auto; overflow-y: auto; + word-break: break-all; margin-right: 0.03in; padding: 10px 0; } From efc9682f23fe91f55dce967a1a769b90643a9843 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 22:15:59 +0000 Subject: [PATCH 16/66] smaller link --- src/webpage/icons/link.svg | 58 +------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/src/webpage/icons/link.svg b/src/webpage/icons/link.svg index ce5b351..92d2712 100644 --- a/src/webpage/icons/link.svg +++ b/src/webpage/icons/link.svg @@ -1,57 +1 @@ - - - - - - - - - + \ No newline at end of file From bb29929470fd26aabf3ca375bcac0a2a8a4b2ca5 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 22:20:04 +0000 Subject: [PATCH 17/66] math made a better icon --- src/webpage/icons/link.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/icons/link.svg b/src/webpage/icons/link.svg index 92d2712..0c55f79 100644 --- a/src/webpage/icons/link.svg +++ b/src/webpage/icons/link.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 144384a5443e6cd11b27278efd8cf71e16a3bbbd Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 22:25:17 +0000 Subject: [PATCH 18/66] even better icon --- src/webpage/icons/link.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/icons/link.svg b/src/webpage/icons/link.svg index 0c55f79..e6e660d 100644 --- a/src/webpage/icons/link.svg +++ b/src/webpage/icons/link.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 45f63bebb72ee4d0c2f4ca06d2dc16285e8559ab Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 22:36:59 +0000 Subject: [PATCH 19/66] use guildDesc --- src/webpage/emoji.ts | 4 ++-- src/webpage/style.css | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 13b12ca..4b705e1 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -157,11 +157,11 @@ class Emoji { guildText.classList.add("flexttb", "guildEmojiText"); const guildName = document.createElement("span"); - guildName.id = "guildName"; + guildName.classList.add("guildName"); guildName.textContent = lookup.guild.name; const guildDesc = document.createElement("span"); - guildDesc.id = "guildDesc"; + guildDesc.classList.add("guildDesc"); const discoverable = lookup.guild.features.find((_) => _ === "DISCOVERABLE"); if (discoverable) { if (lookup.guild.description) { diff --git a/src/webpage/style.css b/src/webpage/style.css index d10d13b..f93a5da 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5197,12 +5197,12 @@ img.error::after { display: flex; justify-content: center; word-break: break-all; - #guildDesc { - color: var(--secondary-text-soft); - font-size: 0.9em; - max-height: 15em; - overflow-y: scroll; - } +} +.guildDesc { + color: var(--secondary-text-soft); + font-size: 0.9em; + max-height: 15em; + overflow-y: scroll; } .optionElement:has(.friendGroupSelect) { From 56b053788b2bc0373992e1ee279ee15fb4c473cf Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 2 Aug 2026 03:41:52 +0000 Subject: [PATCH 20/66] fix: word-break breaking up perfectly fine words Signed-off-by: lizzie --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 381ccaa..9826f0b 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2562,7 +2562,7 @@ span.instanceStatus { flex-shrink: 1; text-wrap: auto; overflow-y: auto; - word-break: break-all; + word-break: break-word; margin-right: 0.03in; padding: 10px 0; } From e73feaeec09b84425d7fb58559e75e2711153b90 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 2 Aug 2026 15:48:09 -0500 Subject: [PATCH 21/66] make typebox into a seperate class for fewer bugs --- src/webpage/channel.ts | 128 +++----------- src/webpage/guild.ts | 10 +- src/webpage/index.ts | 266 +--------------------------- src/webpage/localuser.ts | 28 +-- src/webpage/typeBox.ts | 363 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 400 insertions(+), 395 deletions(-) create mode 100644 src/webpage/typeBox.ts diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 94a74ec..4599df3 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -31,12 +31,12 @@ import {mobile, createImg, safeImg} from "./utils/utils.js"; import {webhookMenu} from "./webhooks.js"; import {File} from "./file.js"; import {Sticker} from "./sticker.js"; -import {CustomHTMLDivElement} from "./index.js"; import {Direct} from "./direct.js"; import {NotificationHandler} from "./notificationHandler.js"; import {Command} from "./interactions/commands.js"; import {Tag} from "./tag.js"; import {CDNParams} from "./utils/cdnParams.js"; +import {TypeBox} from "./typeBox.js"; class Channel extends SnowFlake { editing!: Message | null; @@ -1437,40 +1437,13 @@ class Channel extends SnowFlake { this.replyingto.div.classList.remove("replying"); } this.replyingto = message; - const typebox = document.getElementById("typebox") as HTMLElement; - typebox.focus(); + TypeBox.focus(); if (!this.replyingto?.div) return; - console.log(message); this.replyingto.div.classList.add("replying"); this.makereplybox(); } makereplybox() { - const replybox = document.getElementById("replybox") as HTMLElement; - const typebox = document.getElementById("typebox") as HTMLElement; - if (this.replyingto) { - replybox.innerHTML = ""; - const span = document.createElement("span"); - span.textContent = I18n.replyingTo(this.replyingto.author.username); - const X = document.createElement("button"); - X.onclick = (_) => { - if (this.replyingto?.div) { - this.replyingto.div.classList.remove("replying"); - } - replybox.classList.add("hideReplyBox"); - this.replyingto = null; - replybox.innerHTML = ""; - typebox.classList.remove("typeboxreplying"); - }; - replybox.classList.remove("hideReplyBox"); - X.classList.add("cancelReply", "svgicon", "svg-x"); - replybox.append(span); - replybox.append(X); - typebox.classList.add("typeboxreplying"); - } else { - replybox.classList.add("hideReplyBox"); - replybox.innerHTML = ""; - typebox.classList.remove("typeboxreplying"); - } + TypeBox.updateReplying(); } async getmessage(id: string): Promise { const message = this.messages.get(id); @@ -1543,9 +1516,6 @@ class Channel extends SnowFlake { } static genid: number = 0; nsfwPannel() { - (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + false; - (document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; const messages = document.getElementById("scrollWrap") as HTMLDivElement; const messageContainers = Array.from(messages.getElementsByClassName("messagecontainer")); for (const thing of messageContainers) { @@ -1953,49 +1923,35 @@ class Channel extends SnowFlake { } }; } - files: Blob[] = []; - htmls = new WeakMap(); - textSave = ""; - collectBox() { - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - const [files, html] = this.localuser.fileExtange([], new WeakMap()); - this.files = files; - this.htmls = html; - this.textSave = MarkDown.gatherBoxText(typebox); - typebox.textContent = ""; - } curCommand?: Command; curWatch = () => {}; async submitCommand() { if (!this.curCommand) return; - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - if (await this.curCommand.submit(typebox, this)) { + if (await this.curCommand.submit(TypeBox.box, this)) { this.curCommand = undefined; - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - typebox.markdown.boxEnabled = true; - typebox.innerHTML = ""; - typebox.markdown.boxupdate(); - typebox.removeEventListener("keyup", this.curWatch); + TypeBox.markdown.boxEnabled = true; + TypeBox.box.innerHTML = ""; + TypeBox.markdown.boxupdate(); + TypeBox.box.removeEventListener("keyup", this.curWatch); } } startCommand(command: Command) { this.curCommand = command; - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - typebox.markdown.boxEnabled = false; + TypeBox.markdown.boxEnabled = false; const func = () => { const node = window.getSelection()?.focusNode; if (this.localuser.focusChannel === this) { - const out = command.collect(typebox, this, node || undefined); + const out = command.collect(TypeBox.box, this, node || undefined); if (!out) { - typebox.markdown.boxEnabled = true; - typebox.markdown.boxupdate(); - typebox.removeEventListener("keyup", func); + TypeBox.markdown.boxEnabled = true; + TypeBox.markdown.boxupdate(); + TypeBox.box.removeEventListener("keyup", func); } } }; this.curWatch = func; - typebox.addEventListener("keyup", func); - command.render(typebox, this); + TypeBox.box.addEventListener("keyup", func); + command.render(TypeBox.box, this); } isForum() { return this.type === 15 || this.type === 16; @@ -2624,28 +2580,18 @@ class Channel extends SnowFlake { if (this.owner instanceof Direct) { this.owner.freindDiv?.classList.remove("viewChannel"); } - if (this.localuser.focusChannel) { - this.localuser.focusChannel.collectBox(); + TypeBox.saveBox(); + if (!this.curCommand && !this.isForum()) { + TypeBox.restoreBox(this); } - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - typebox.markdown.boxEnabled = !this.curCommand; + TypeBox.markdown.boxEnabled = !this.curCommand; if (this.curCommand) { - this.curCommand.render(typebox, this); + this.curCommand.render(TypeBox.box, this); } - typebox.style.setProperty( + TypeBox.box.style.setProperty( "--channel-text", JSON.stringify(I18n.channel.typebox(this.shortName)), ); - if (!this.curCommand && !this.isForum()) { - const md = typebox.markdown; - md.owner = this; - typebox.textContent = this.textSave; - md.boxupdate(Infinity); - } - if (this.isForum()) { - typebox.textContent = ""; - } - this.localuser.fileExtange(this.files, this.htmls); if (getMessages === undefined) { getMessages = this.type !== 2 || !this.localuser.voiceAllowed; @@ -2761,36 +2707,8 @@ class Channel extends SnowFlake { } this.rendertyping(); - try { - (document.getElementById("typebox") as HTMLDivElement).contentEditable = this.canMessage - ? "plaintext-only" - : "false"; - } catch { - (document.getElementById("typebox") as HTMLDivElement).contentEditable = this.canMessage - ? "true" - : "false"; - } - (document.getElementById("upload") as HTMLElement).style.visibility = this.canMessage - ? "visible" - : "hidden"; - (document.getElementById("gifTB") as HTMLElement).style.display = this.canMessage - ? "block" - : "none"; - (document.getElementById("stickerTB") as HTMLElement).style.display = this.canMessage - ? "block" - : "none"; - (document.getElementById("emojiTB") as HTMLElement).style.display = this.canMessage - ? "block" - : "none"; - (document.getElementById("mobileSend") as HTMLElement).style.display = this.canMessage - ? "block" - : "none"; - (document.getElementById("typediv") as HTMLElement).style.visibility = "visible"; - if (!mobile) { - (document.getElementById("typebox") as HTMLDivElement).focus(); - } else { - (document.getElementById("typebox") as HTMLDivElement).blur(); - } + TypeBox.changeWrite(); + try { if (getMessages) await this.putmessages(); } catch (e) { diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 962b479..a861252 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -35,6 +35,7 @@ import {Hover} from "./hover.js"; import {ReportMenu} from "./reporting/report.js"; import {getDeveloperSettings} from "./utils/storage/devSettings.js"; import {CDNParams} from "./utils/cdnParams.js"; +import {TypeBox} from "./typeBox.js"; export async function makeInviteMenu(inviteMenu: Options, guild: Guild, url: string) { const invDiv = document.createElement("div"); const bansp = ProgessiveDecodeJSON(url, { @@ -1955,16 +1956,9 @@ class Guild extends SnowFlake { if (this.localuser.focusChannel && this.localuser.focusChannel.myhtml) { this.localuser.focusChannel.myhtml.classList.remove("viewChannel"); } + TypeBox.changeVisablity(false); this.prevchannel = undefined; this.localuser.focusChannel = undefined; - const replybox = document.getElementById("replybox") as HTMLElement; - const typebox = document.getElementById("typebox") as HTMLElement; - replybox.classList.add("hideReplyBox"); - typebox.classList.remove("typeboxreplying"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = "false"; - (document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; - (document.getElementById("sideDiv") as HTMLElement).innerHTML = ""; } noChannel(addstate: boolean) { for (const c of this.channels) { diff --git a/src/webpage/index.ts b/src/webpage/index.ts index e6aa6ff..2388a98 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -3,8 +3,6 @@ import {Contextmenu} from "./contextmenu.js"; import {mobile, Specialuser} from "./utils/utils.js"; import {setTheme} from "./utils/utils.js"; import {MarkDown} from "./markdown.js"; -import {Message} from "./message.js"; -import {File} from "./file.js"; import {I18n} from "./i18n.js"; import "./utils/pollyfills.js"; import {makeLogin} from "./login.js"; @@ -17,8 +15,8 @@ import "./invite.js"; import "./oauth2/auth.js"; import "./audio/page.js"; import "./404.js"; -import {Channel} from "./channel.js"; import type * as C from "./typeChecker/chekerIndex.js"; +import {TypeBox} from "./typeBox.js"; if (window.location.pathname === "/app") { window.location.pathname = "/channels/@me"; @@ -72,35 +70,7 @@ if (window.location.pathname.startsWith("/channels")) { }); let thisUser: Localuser | null = null; - function regSwap(l: Localuser) { - l.onswap = (l) => { - thisUser = l; - regSwap(l); - }; - l.fileExtange = (img, html) => { - const blobArr: Blob[] = []; - const htmlArr = imagesHtml; - let i = 0; - for (const file of images) { - const img = imagesHtml.get(file); - if (!img) continue; - if (pasteImageElement.contains(img)) { - pasteImageElement.removeChild(img); - blobArr.push(images[i]); - } else { - i++; - } - } - images = img; - imagesHtml = html; - for (const file of images) { - const img = imagesHtml.get(file); - if (!img) throw new Error("Image without HTML, exiting"); - pasteImageElement.append(img); - } - return [blobArr, htmlArr]; - }; - } + const loaddesc = document.getElementById("load-desc") as HTMLSpanElement; try { const current = sessionStorage.getItem("currentuser") || Localuser.users.currentuser; @@ -110,8 +80,8 @@ if (window.location.pathname.startsWith("/channels")) { thisUser = new Localuser(Localuser.users.users[current]); } - regSwap(thisUser); thisUser.initwebsocket().then(async () => { + if (thisUser) TypeBox.regSwap(thisUser); thisUser?.loaduser(); console.warn("huh"); await thisUser?.init(); @@ -160,151 +130,16 @@ if (window.location.pathname.startsWith("/channels")) { }, }, ); - const channelw = document.getElementById("channelw"); - if (channelw) - channelw.addEventListener("keypress", (e) => { - if (e.ctrlKey || e.altKey || e.metaKey || e.metaKey) return; - let owner = e.target as HTMLElement; - while (owner !== channelw) { - if (owner.tagName === "input" || owner.contentEditable !== "false") { - return; - } - owner = owner.parentElement as HTMLElement; - } - typebox.markdown.boxupdate(Infinity); - }); + menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement); - const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement; - let replyingTo: Message | null = null; window.addEventListener("popstate", (e) => { if (e.state instanceof Object) { thisUser?.goToState(e.state); } //console.log(e.state,"state:3") }); - let nonceMap = new Map(); - //@ts-expect-error unused right now, not needed - function getNonce(id: string) { - const nonce = nonceMap.get(id) || Math.floor(Math.random() * 1000000000) + ""; - nonceMap.set(id, nonce); - return nonce; - } - const markdown = new MarkDown("", thisUser ?? undefined); - async function sendMessage(channel: Channel, content: string) { - if (!channel.canMessageRightNow()) return; - if (channel.curCommand) { - channel.submitCommand(); - return; - } - markdown.onUpdate("", false); - replyingTo = thisUser?.focusChannel ? thisUser.focusChannel.replyingto : null; - if (replyingTo?.div) { - replyingTo.div.classList.remove("replying"); - } - if (thisUser?.focusChannel) { - thisUser.focusChannel.replyingto = null; - thisUser.focusChannel.makereplybox(); - } - const attachments = images.filter((_) => document.contains(imagesHtml.get(_) || null)); - while (images.length) { - const elm = imagesHtml.get(images.pop() as Blob) as HTMLElement; - if (pasteImageElement.contains(elm)) pasteImageElement.removeChild(elm); - } - typebox.innerHTML = ""; - typebox.markdown.txt = []; - try { - await new Promise((mres, rej) => - channel.sendMessage( - content, - { - attachments, - embeds: [], // Add an empty array for the embeds property - replyingto: replyingTo, - sticker_ids: [], - //nonce: getNonce(channel.id), - }, - (res) => { - if (res === "Ok") { - mres(); - } else { - rej(); - } - }, - ), - ); - } catch { - images = attachments; - for (const file of images) { - const img = imagesHtml.get(file); - if (!img) continue; - pasteImageElement.append(img); - } - channel.replyingto = replyingTo; - channel.makereplybox(); - typebox.textContent = content; - typebox.markdown.txt = content.split(""); - typebox.markdown.boxupdate(Infinity); - } - nonceMap.delete(channel.id); - } - const mobileSend = document.getElementById("mobileSend"); - if (mobileSend) { - mobileSend.onclick = () => { - const channel = thisUser?.focusChannel; - if (!channel) return; - const content = MarkDown.gatherBoxText(typebox); - sendMessage(channel, content); - }; - } - async function handleEnter(event: KeyboardEvent): Promise { - if (event.isComposing) return; - if (event.key === "Escape" && (images.length || thisUser?.focusChannel?.replyingto)) { - while (images.length) { - const elm = imagesHtml.get(images.pop() as Blob) as HTMLElement; - if (pasteImageElement.contains(elm)) pasteImageElement.removeChild(elm); - } - if (thisUser?.focusChannel) { - thisUser.focusChannel?.replyingto?.div?.classList.remove("replying"); - thisUser.focusChannel.replyingto = null; - thisUser.focusChannel.makereplybox(); - } - thisUser?.updateSend(); - return; - } - if (thisUser?.handleKeyUp(event)) { - return; - } - - const channel = thisUser?.focusChannel; - if (!channel) return; - const content = MarkDown.gatherBoxText(typebox); - if (content === "" && event.key === "ArrowUp") { - channel.editLast(); - return; - } - channel.typingstart(); - - if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { - event.preventDefault(); - await sendMessage(channel, content); - } - } - - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - - typebox.markdown = markdown; - typebox.addEventListener("keyup", handleEnter); - typebox.addEventListener("keydown", (event) => { - if (event.isComposing) return; - thisUser?.keydown(event); - if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { - event.preventDefault(); - event.stopImmediatePropagation(); - } - }); - markdown.giveBox(typebox); { const searchBox = document.getElementById("searchBox") as CustomHTMLDivElement; const markdown = new MarkDown("", thisUser ?? undefined); @@ -352,25 +187,6 @@ if (window.location.pathname.startsWith("/channels")) { return span; }); } - let images: Blob[] = []; - let imagesHtml = new WeakMap(); - - document.addEventListener("paste", async (e: ClipboardEvent) => { - if (!thisUser?.focusChannel) return; - if (!e.clipboardData) return; - - for (const file of Array.from(e.clipboardData.files)) { - const fileInstance = File.initFromBlob(file); - e.preventDefault(); - const html = fileInstance.upHTML(images, imagesHtml, file, () => { - thisUser?.updateSend(); - }); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.set(file, html); - } - thisUser?.updateSend(); - }); await setTheme(); @@ -396,60 +212,7 @@ if (window.location.pathname.startsWith("/channels")) { }; memberListToggle.checked = false; } - let dragendtimeout = setTimeout(() => {}); - document.addEventListener("dragover", (e) => { - clearTimeout(dragendtimeout); - const data = e.dataTransfer; - const bg = document.getElementById("gimmefile") as HTMLDivElement; - if (data) { - const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file"); - if (!isfile) { - bg.hidden = true; - return; - } - e.preventDefault(); - bg.hidden = false; - //console.log(data.types,data) - } else { - bg.hidden = true; - } - }); - document.addEventListener("dragleave", (_) => { - dragendtimeout = setTimeout(() => { - const bg = document.getElementById("gimmefile") as HTMLDivElement; - bg.hidden = true; - }, 1000); - }); - document.addEventListener("dragenter", (e) => { - e.preventDefault(); - }); - document.addEventListener("drop", (e) => { - const data = e.dataTransfer; - const bg = document.getElementById("gimmefile") as HTMLDivElement; - bg.hidden = true; - if (!thisUser?.focusChannel) { - e.preventDefault(); - return; - } - if (data) { - const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file"); - if (isfile) { - e.preventDefault(); - console.log(data.files); - for (const file of Array.from(data.files)) { - const fileInstance = File.initFromBlob(file); - const html = fileInstance.upHTML(images, imagesHtml, file, () => { - thisUser?.updateSend(); - }); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.set(file, html); - } - thisUser?.updateSend(); - } - } - }); const pinnedM = document.getElementById("pinnedM") as HTMLElement; pinnedM.onclick = (e) => { thisUser?.pinnedClick(pinnedM.getBoundingClientRect()); @@ -468,26 +231,7 @@ if (window.location.pathname.startsWith("/channels")) { }, ); umenu.addButton(I18n.upload(), () => { - const input = document.createElement("input"); - input.type = "file"; - input.click(); - input.multiple = true; - console.log("clicked"); - if (!thisUser?.focusChannel) return; - input.onchange = () => { - if (input.files) { - for (const file of Array.from(input.files)) { - const fileInstance = File.initFromBlob(file); - const html = fileInstance.upHTML(images, imagesHtml, file, () => { - thisUser?.updateSend(); - }); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.set(file, html); - } - thisUser?.updateSend(); - } - }; + TypeBox.uploadFiles(); }); umenu.bindContextmenu( document.getElementById("upload")!, diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 7d7a182..85348b8 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -53,15 +53,14 @@ import {trimTrailingSlashes} from "./utils/netUtils.js"; import {Versions} from "./versions.js"; import {Shortcut} from "./shortcuts/shortcut.js"; import {getShortcuts, setShortcuts} from "./utils/storage/shortcuts.js"; +import {TypeBox} from "./typeBox.js"; type traceObj = { micros: number; calls?: (string | traceObj)[]; }; type trace = [string, traceObj]; const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]); -interface CustomHTMLDivElement extends HTMLDivElement { - markdown: MarkDown; -} + interface MDSearchOption { name: string; replace: string; @@ -1755,6 +1754,7 @@ class Localuser { } loadGuild(id: string, forceReload = false): Guild | undefined { + TypeBox.saveBox(); this.searching = false; let guild = this.guilds.get(id); if (!guild) { @@ -4280,21 +4280,12 @@ class Localuser { }); } updateSend() { - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - if ( - (typebox.markdown.rawString && typebox.markdown.rawString !== "\n") || - document.getElementById("pasteimage")?.children.length - ) { - typebox.parentElement!.classList.remove("noConent"); - } else { - typebox.parentElement!.classList.add("noConent"); - } + TypeBox.updateSend(); } //TODO make this an option readonly autofillregex = Object.freeze(/(^|\s|\n)[@#:]([a-zA-Z0-9]*)$/i); mdBox() { - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - const typeMd = typebox.markdown; + const typeMd = TypeBox.markdown; typeMd.owner = this; typeMd.onUpdate = (str, pre) => { this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre); @@ -4529,8 +4520,7 @@ class Localuser { search.focus(); } async TBEmojiMenu(rect: DOMRect) { - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - const p = saveCaretPosition(typebox); + const p = TypeBox.saveCarrot(); if (!p) return; const original = MarkDown.getText(); @@ -4541,7 +4531,7 @@ class Localuser { ); this.favorites.addEmoji(emoji.id || (emoji.emoji as string)); p(); - const md = typebox.markdown; + const md = TypeBox.markdown; this.MDReplace( emoji.id ? `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>` @@ -4576,10 +4566,6 @@ class Localuser { ); } } - fileExtange!: ( - files: Blob[], - html: WeakMap, - ) => [Blob[], WeakMap]; MDSearchOptions( options: MDSearchOption[], original: string, diff --git a/src/webpage/typeBox.ts b/src/webpage/typeBox.ts new file mode 100644 index 0000000..49f7abe --- /dev/null +++ b/src/webpage/typeBox.ts @@ -0,0 +1,363 @@ +import {Channel} from "./channel"; +import {Localuser} from "./localuser"; +import {MarkDown, saveCaretPosition} from "./markdown"; +import {File} from "./file"; +import {I18n} from "./i18n"; +import {mobile} from "./utils/utils"; +class BoxState { + text: string; + files: globalThis.File[]; + constructor(text: string, files: globalThis.File[]) { + this.text = text; + this.files = files; + } +} +export class TypeBox { + static box = document.getElementById("typebox") as HTMLDivElement; + private static localuser?: Localuser; + static markdown = new MarkDown(""); + private static files: globalThis.File[] = []; + private static imagesHtml = new WeakMap(); + private static pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement; + private static nonceMap = new Map(); + static getNonce(id: string) { + const nonce = this.nonceMap.get(id) || Math.floor(Math.random() * 1000000000) + ""; + this.nonceMap.set(id, nonce); + return nonce; + } + static focus() { + this.box.focus(); + } + static init() { + this.box.addEventListener("keyup", this.handleEnter.bind(this)); + this.box.addEventListener("keydown", (event) => { + if (event.isComposing) return; + this.localuser?.keydown(event); + if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + }); + this.markdown.giveBox(this.box); + const mobileSend = document.getElementById("mobileSend"); + if (mobileSend) { + mobileSend.onclick = () => { + const channel = this.localuser?.focusChannel; + if (!channel) return; + const content = MarkDown.gatherBoxText(this.box); + this.sendMessage(channel, content); + }; + } + const channelw = document.getElementById("channelw"); + if (channelw) + channelw.addEventListener("keypress", (e) => { + if (e.ctrlKey || e.altKey || e.metaKey || e.metaKey) return; + let owner = e.target as HTMLElement; + while (owner !== channelw) { + if (owner.tagName === "input" || owner.contentEditable !== "false") { + return; + } + owner = owner.parentElement as HTMLElement; + } + this.markdown.boxupdate(Infinity); + }); + document.addEventListener("paste", async (e: ClipboardEvent) => { + if (!this.localuser?.focusChannel) return; + if (!e.clipboardData) return; + + for (const file of Array.from(e.clipboardData.files)) { + e.preventDefault(); + this.addFile(file); + } + this.localuser?.updateSend(); + }); + let dragendtimeout = setTimeout(() => {}); + document.addEventListener("dragover", (e) => { + clearTimeout(dragendtimeout); + const data = e.dataTransfer; + const bg = document.getElementById("gimmefile") as HTMLDivElement; + + if (data) { + const isfile = + data.types.includes("Files") || data.types.includes("application/x-moz-file"); + if (!isfile) { + bg.hidden = true; + return; + } + e.preventDefault(); + bg.hidden = false; + //console.log(data.types,data) + } else { + bg.hidden = true; + } + }); + document.addEventListener("dragleave", (_) => { + dragendtimeout = setTimeout(() => { + const bg = document.getElementById("gimmefile") as HTMLDivElement; + bg.hidden = true; + }, 1000); + }); + document.addEventListener("dragenter", (e) => { + e.preventDefault(); + }); + document.addEventListener("drop", (e) => { + const data = e.dataTransfer; + const bg = document.getElementById("gimmefile") as HTMLDivElement; + bg.hidden = true; + if (!this.localuser?.focusChannel) { + e.preventDefault(); + return; + } + if (data) { + const isfile = + data.types.includes("Files") || data.types.includes("application/x-moz-file"); + if (isfile) { + e.preventDefault(); + console.log(data.files); + for (const file of Array.from(data.files)) { + this.addFile(file); + } + this.localuser?.updateSend(); + } + } + }); + } + private static cMap = new Map(); + private static channelToID(c: Channel) { + return c.id; + } + static saveBox() { + if (!this.localuser?.focusChannel) return; + const state = new BoxState(MarkDown.gatherBoxText(this.box), this.files); + this.pasteImageElement.textContent = ""; + this.box.textContent = ""; + this.files = []; + this.markdown.txt = []; + this.cMap.set(this.channelToID(this.localuser.focusChannel), state); + } + static restoreBox(c = this.localuser?.focusChannel) { + if (!c) return; + const state = this.cMap.get(this.channelToID(c)); + this.box.textContent = ""; + this.pasteImageElement.textContent = ""; + if (state) { + this.markdown.txt = [...state.text]; + this.files = state.files; + } else { + this.markdown.txt = []; + this.files = []; + } + this.box.append(this.markdown.makeHTML({keep: true})); + this.markdown.owner = c; + this.markdown.boxupdate(Infinity); + for (const file of this.files) { + this.addFile(file, false); + } + } + static changeWrite() { + if (!this.localuser?.focusChannel) return; + const c = this.localuser.focusChannel; + const canMessage = c.canMessage; + try { + this.box.contentEditable = canMessage ? "plaintext-only" : "false"; + } catch { + this.box.contentEditable = canMessage ? "true" : "false"; + } + (document.getElementById("upload") as HTMLElement).style.visibility = canMessage + ? "visible" + : "hidden"; + (document.getElementById("gifTB") as HTMLElement).style.display = canMessage ? "block" : "none"; + (document.getElementById("stickerTB") as HTMLElement).style.display = canMessage + ? "block" + : "none"; + (document.getElementById("emojiTB") as HTMLElement).style.display = canMessage + ? "block" + : "none"; + (document.getElementById("mobileSend") as HTMLElement).style.display = canMessage + ? "block" + : "none"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "visible"; + if (!mobile) { + this.box.focus(); + } else { + this.box.blur(); + } + } + static saveCarrot() { + return saveCaretPosition(this.box); + } + static updateSend() { + if ( + (this.markdown.rawString && this.markdown.rawString !== "\n") || + document.getElementById("pasteimage")?.children.length + ) { + this.box.parentElement!.classList.remove("noConent"); + } else { + this.box.parentElement!.classList.add("noConent"); + } + } + static changeVisablity(visable: boolean) { + if (visable) { + } else { + this.box.contentEditable = "" + false; + const replybox = document.getElementById("replybox") as HTMLElement; + replybox.classList.add("hideReplyBox"); + this.box.classList.remove("typeboxreplying"); + (document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; + (document.getElementById("sideDiv") as HTMLElement).innerHTML = ""; + } + } + static addFile(blob: globalThis.File, add = true) { + const file = File.initFromBlob(blob); + const html = file.upHTML(this.files, this.imagesHtml, blob, () => { + this.localuser?.updateSend(); + }); + this.pasteImageElement.appendChild(html); + if (add) this.files.push(blob); + this.imagesHtml.set(blob, html); + } + static updateReplying() { + const c = this.localuser?.focusChannel; + const replybox = document.getElementById("replybox") as HTMLElement; + if (c && c.replyingto) { + this.box.classList.add("typeboxreplying"); + replybox.innerHTML = ""; + const span = document.createElement("span"); + span.textContent = I18n.replyingTo(c.replyingto.author.username); + const X = document.createElement("button"); + X.onclick = (_) => { + if (c.replyingto?.div) { + c.replyingto.div.classList.remove("replying"); + } + replybox.classList.add("hideReplyBox"); + c.replyingto = null; + replybox.innerHTML = ""; + TypeBox.updateReplying(); + }; + replybox.classList.remove("hideReplyBox"); + X.classList.add("cancelReply", "svgicon", "svg-x"); + replybox.append(span); + replybox.append(X); + } else { + replybox.classList.add("hideReplyBox"); + replybox.innerHTML = ""; + this.box.classList.remove("typeboxreplying"); + } + } + static uploadFiles() { + const input = document.createElement("input"); + input.type = "file"; + input.click(); + input.multiple = true; + console.log("clicked"); + if (!this.localuser?.focusChannel) return; + input.onchange = () => { + if (input.files) { + for (const file of Array.from(input.files)) { + this.addFile(file); + } + this.localuser?.updateSend(); + } + }; + } + static regSwap(l: Localuser) { + l.onswap = (l) => { + this.localuser = l; + this.regSwap(l); + }; + this.localuser = l; + } + private static async handleEnter(event: KeyboardEvent): Promise { + if (event.isComposing) return; + if (event.key === "Escape" && (this.files.length || this.localuser?.focusChannel?.replyingto)) { + while (this.files.length) { + const elm = this.imagesHtml.get(this.files.pop() as Blob) as HTMLElement; + if (this.pasteImageElement.contains(elm)) this.pasteImageElement.removeChild(elm); + } + if (this.localuser?.focusChannel) { + this.localuser.focusChannel?.replyingto?.div?.classList.remove("replying"); + this.localuser.focusChannel.replyingto = null; + this.localuser.focusChannel.makereplybox(); + } + this.localuser?.updateSend(); + return; + } + if (this.localuser?.handleKeyUp(event)) { + return; + } + + const channel = this.localuser?.focusChannel; + if (!channel) return; + const content = MarkDown.gatherBoxText(this.box); + if (content === "" && event.key === "ArrowUp") { + channel.editLast(); + return; + } + channel.typingstart(); + + if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { + event.preventDefault(); + await this.sendMessage(channel, content); + } + } + private static async sendMessage(channel: Channel, content: string) { + if (!channel.canMessageRightNow()) return; + if (channel.curCommand) { + channel.submitCommand(); + return; + } + this.markdown.onUpdate("", false); + + let replyingTo = this.localuser?.focusChannel ? this.localuser.focusChannel.replyingto : null; + if (replyingTo?.div) { + replyingTo.div.classList.remove("replying"); + } + if (this.localuser?.focusChannel) { + this.localuser.focusChannel.replyingto = null; + this.localuser.focusChannel.makereplybox(); + } + const attachments = this.files.filter((_) => document.contains(this.imagesHtml.get(_) || null)); + while (this.files.length) { + const elm = this.imagesHtml.get(this.files.pop() as Blob) as HTMLElement; + if (this.pasteImageElement.contains(elm)) this.pasteImageElement.removeChild(elm); + } + this.box.innerHTML = ""; + this.markdown.txt = []; + try { + await new Promise((mres, rej) => + channel.sendMessage( + content, + { + attachments, + embeds: [], // Add an empty array for the embeds property + replyingto: replyingTo, + sticker_ids: [], + //nonce: getNonce(channel.id), + }, + (res) => { + if (res === "Ok") { + mres(); + } else { + rej(); + } + }, + ), + ); + } catch { + this.files = attachments; + for (const file of this.files) { + const img = this.imagesHtml.get(file); + if (!img) continue; + this.pasteImageElement.append(img); + } + channel.replyingto = replyingTo; + channel.makereplybox(); + this.box.textContent = content; + this.markdown.txt = content.split(""); + this.markdown.boxupdate(Infinity); + } + this.nonceMap.delete(channel.id); + } +} +TypeBox.init(); From 961417265f4b03b4f9cb236f62fc7c0faf018e14 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 2 Aug 2026 15:50:45 -0500 Subject: [PATCH 22/66] fix TSC errors --- src/webpage/channel.ts | 2 +- src/webpage/discovery.ts | 3 ++- src/webpage/localuser.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 4599df3..74ddf6c 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -27,7 +27,7 @@ import {Member} from "./member.js"; import {Voice} from "./voice.js"; import {User, userVolMenu} from "./user.js"; import {I18n} from "./i18n.js"; -import {mobile, createImg, safeImg} from "./utils/utils.js"; +import {createImg, safeImg} from "./utils/utils.js"; import {webhookMenu} from "./webhooks.js"; import {File} from "./file.js"; import {Sticker} from "./sticker.js"; diff --git a/src/webpage/discovery.ts b/src/webpage/discovery.ts index 21dc185..fcde1a1 100644 --- a/src/webpage/discovery.ts +++ b/src/webpage/discovery.ts @@ -4,6 +4,7 @@ import {I18n} from "./i18n.js"; import {guildjson} from "./jsontypes.js"; import {ReportMenu} from "./reporting/report.js"; import {Dialog} from "./settings.js"; +import {TypeBox} from "./typeBox.js"; import {CDNParams} from "./utils/cdnParams.js"; import {getDeveloperSettings} from "./utils/storage/devSettings.js"; import {createImg} from "./utils/utils.js"; @@ -51,7 +52,7 @@ export class Discovery { this.owner.freindDiv?.classList.remove("viewChannel"); } if (this.localuser.focusChannel) { - this.localuser.focusChannel.collectBox(); + TypeBox.saveBox(); } history.pushState(["@me", "discover"], "", "/channels/@me/discover"); this.localuser.pageTitle(I18n.discovery()); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 85348b8..4e4ea5f 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -23,7 +23,7 @@ import { } from "./jsontypes.js"; import {Member} from "./member.js"; import {buttonColor, Dialog, Form, FormError, Options, Settings} from "./settings.js"; -import {getTextNodeAtPosition, MarkDown, saveCaretPosition} from "./markdown.js"; +import {getTextNodeAtPosition, MarkDown} from "./markdown.js"; import {Bot} from "./bot.js"; import {Role} from "./role.js"; import {VoiceFactory, voiceStatusStr} from "./voice.js"; From cbd620b8645614b574f87c78131d85fbc8724b70 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 2 Aug 2026 17:01:47 -0500 Subject: [PATCH 23/66] blur a bit --- src/webpage/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index a23184b..6d4de24 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1058,8 +1058,9 @@ textarea { right: 5px; transition: top 0.2s; transition-timing-function: ease-in; + backdrop-filter: blur(20px); mix-blend-mode: difference; - background: white; + background: #969696; cursor: pointer; &.favorited { background: orange; From 9fd285f4fa73012a7974c0d56211a478225a0bd2 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 05:51:26 +0000 Subject: [PATCH 24/66] fix: syntax highlight on white mode Signed-off-by: lizzie --- src/webpage/style.css | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 381ccaa..4d14621 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3386,32 +3386,32 @@ img.bigembedimg { } } ::highlight(highkeyword) { - color: yellow; + color: var(--yellow); } ::highlight(highnumber) { - color: orange; + color: color-mix(in srgb, var(--yellow) 50%, var(--red)); } ::highlight(highstring) { - color: red; + color: color-mix(in srgb, var(--red) 50%, var(--primary-text)); } ::highlight(highsymbol) { - color: white; + color: var(--primary-text-soft); font-weight: bold; } ::highlight(highcomment) { - color: green; + color: color-mix(in srgb, var(--green) 50%, var(--primary-text)); } ::highlight(highidentifier) { - color: white; + color: var(--primary-text); } ::highlight(highClass) { - color: #6741ff; + color: color-mix(in srgb, var(--red) 25%, var(--primary-text)); } ::highlight(highProp) { - color: #33dcff; + color: color-mix(in srgb, var(--blue) 50%, var(--primary-text)); } ::highlight(highFunc) { - color: #d564ff; + color: color-mix(in srgb, var(--red) 50%, var(--primary-text)); } #memberlisttoggle.svgicon { -moz-appearance: initial; From 1de56b9a1fd38251973c9e82e95935390931e301 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 05:53:46 +0000 Subject: [PATCH 25/66] fix yellow --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 4d14621..f857988 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3386,7 +3386,7 @@ img.bigembedimg { } } ::highlight(highkeyword) { - color: var(--yellow); + color: color-mix(in srgb, var(--yellow) 75%, var(--primary-text)); } ::highlight(highnumber) { color: color-mix(in srgb, var(--yellow) 50%, var(--red)); From c3f3914931874d91352f48ec914a59072c293b1c Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 06:35:29 +0000 Subject: [PATCH 26/66] BETTER code highlight --- src/webpage/style.css | 17 +++++++++-------- src/webpage/themes.css | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index f857988..fcff672 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3386,33 +3386,34 @@ img.bigembedimg { } } ::highlight(highkeyword) { - color: color-mix(in srgb, var(--yellow) 75%, var(--primary-text)); + color: var(--code-keyword); } ::highlight(highnumber) { - color: color-mix(in srgb, var(--yellow) 50%, var(--red)); + color: var(--code-number); } ::highlight(highstring) { - color: color-mix(in srgb, var(--red) 50%, var(--primary-text)); + color: var(--code-string); } ::highlight(highsymbol) { - color: var(--primary-text-soft); + color: var(--primary-text-prominent); font-weight: bold; } ::highlight(highcomment) { - color: color-mix(in srgb, var(--green) 50%, var(--primary-text)); + color: var(--code-comment); } ::highlight(highidentifier) { color: var(--primary-text); } ::highlight(highClass) { - color: color-mix(in srgb, var(--red) 25%, var(--primary-text)); + color: var(--code-class); } ::highlight(highProp) { - color: color-mix(in srgb, var(--blue) 50%, var(--primary-text)); + color: var(--code-prop); } ::highlight(highFunc) { - color: color-mix(in srgb, var(--red) 50%, var(--primary-text)); + color: var(--code-func); } + #memberlisttoggle.svgicon { -moz-appearance: initial; &::before { diff --git a/src/webpage/themes.css b/src/webpage/themes.css index e373477..16eb6b3 100644 --- a/src/webpage/themes.css +++ b/src/webpage/themes.css @@ -12,6 +12,14 @@ --blue: #779bff; --grey: #b5b5b5; --update: var(--green); + + --code-keyword: yellow; + --code-number: orange; + --code-string: red; + --code-comment: green; + --code-class: #6741ff; + --code-prop: #33dcff; + --code-func: #d564ff; } /* Themes. See themes.txt */ @@ -88,6 +96,15 @@ --secondary-text-soft: #4c4c5a; --dock-bg: #d1d1df; --dock-hover: #b8b8d0; + + --code-bg: #fdfdf4; + --code-keyword: rgb(180, 180, 76); + --code-number: rgb(161, 109, 12); + --code-string: rgb(153, 4, 4); + --code-comment: rgb(9, 116, 9); + --code-class: #230a86; + --code-prop: #027d96; + --code-func: #6c0091; } .Light-theme { From 8731fe7b733a4ea0186c6b65e2c2dcee8d0f325d Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 06:38:19 +0000 Subject: [PATCH 27/66] fix small tiny issue with parser --- src/webpage/highlighter/clike/lex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/highlighter/clike/lex.ts b/src/webpage/highlighter/clike/lex.ts index 34dde2c..0de49b9 100644 --- a/src/webpage/highlighter/clike/lex.ts +++ b/src/webpage/highlighter/clike/lex.ts @@ -25,7 +25,7 @@ function regex(config: clikeConf): RegExp { `(${RegExp.escape(config.multiLine)}(?:${matchOpts.join("|")})*(?:${RegExp.escape(config.multiLine)})?)`, ); } - conds.push(`\'(\\\\(.|\\n)|[^"\\n\\\\])*\'?`); + conds.push(`\'(\\\\(.|\\n)|[^\'\\n\\\\])*\'?`); conds.push(`"(\\\\(.|\\n)|[^"\\n\\\\])*"?`); if (config.hashComments) { conds.push("#.*"); From 561940e2880f0c252836dca74f10049584e10e35 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 06:45:05 +0000 Subject: [PATCH 28/66] fix: friend button on white mode Signed-off-by: lizzie --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 381ccaa..bff04ef 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1308,7 +1308,7 @@ textarea { position: absolute; right: 10px; top: 10px; - background: #0000009e; + background: var(--secondary-bg); padding: 6px; border-radius: 100%; cursor: pointer; From ea3eae478c9ceef9fe3c82f059da804d48c247fe Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 3 Aug 2026 06:48:40 +0000 Subject: [PATCH 29/66] fix: banner text colours on white mode Signed-off-by: lizzie --- src/webpage/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 381ccaa..eb62bf7 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2086,6 +2086,7 @@ span.instanceStatus { align-items: start; padding-top: 10px; background-size: cover; + color: white; .ellipsis { text-shadow: 0px 0px 8px black; } From 9f4ea79261d6e0e56a4669077ab0ef3b2eab5044 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 3 Aug 2026 14:35:56 +0200 Subject: [PATCH 30/66] Localisation updates from https://translatewiki.net. --- translations/fr.json | 21 +++++++++++++-------- translations/it.json | 7 +++++++ translations/ko.json | 10 ++++++++-- translations/lb.json | 11 ++++++++++- translations/nl.json | 19 +++++++++++++++---- translations/zh-hans.json | 11 ++++++++++- 6 files changed, 63 insertions(+), 16 deletions(-) diff --git a/translations/fr.json b/translations/fr.json index 740b33c..93a27a7 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -16,6 +16,7 @@ "Majda GHARRABOU", "McDutchie", "Momo50WM", + "NClara", "Od1n", "Oh64", "Pyschobbens", @@ -147,20 +148,21 @@ "cancel": "Annuler", "channel": { "SlowmodeCool": "Temporisation du mode lent : $1", + "copyURL": "Copier l'URL du canal", "TimeOutCool": "Exclu jusqu'à : $1", "allowIcons": "Autoriser les icônes de chaîne personnalisées", "announcement": "Annonces", - "copyId": "Copier l'identifiant de la chaîne", + "copyId": "Copier l'identifiant du canal", "copyIdCat": "Copier l'identifiant de la catégorie", "createCatagory": "Créer une catégorie", - "createChannel": "Créer un chaîne", - "creating": "Création du Chaîne", + "createChannel": "Créer un canal", + "creating": "Création du canal", "delete": "Supprimer la chaîne", "deleteCat": "Supprimer la catégorie", "icon": "Icône :", "makeInvite": "Créer une invitation", "markRead": "Marquer comme lu", - "mute": "Rendre la chaîne muette", + "mute": "Rendre le canal muet", "name": "Chaîne", "name:": "Nom de la Chaîne :", "nsfw:": "Avertissement contenu mature :", @@ -174,7 +176,7 @@ "text": "Texte", "timedOutUntil": "Exclu jusqu'à : $1", "topic:": "Sujet :", - "typebox": "Envoyer un message dans $1", + "typebox": "Message dans # $1", "unmute": "Rétablir les notifications de la chaîne", "voice": "Voix", "deleteThread": "Supprimer le fil", @@ -763,7 +765,8 @@ "REQUEST_TO_SPEAK": "Permet aux membres du rôle de demander à parler dans le canal de scène.", "USE_EMBEDDED_ACTIVITIES": "Permet aux membres du rôle d'utiliser les activités intégrées.", "USE_APPLICATION_COMMANDS": "Permet aux membres du rôle d'utiliser les commandes d'application.", - "USE_EXTERNAL_APPS": "Permet aux applications des membres du rôle d'envoyer des réponses publiques. (Si désactivé, les membres du rôle seront toujours autorisés à utiliser leurs applications mais les réponses seront visible que d'eux-mêmes. Cela s'applique uniquement aux applications qui ne sont pas également installées dans la guilde)." + "USE_EXTERNAL_APPS": "Permet aux applications des membres du rôle d'envoyer des réponses publiques. (Si désactivé, les membres du rôle seront toujours autorisés à utiliser leurs applications mais les réponses seront visible que d'eux-mêmes. Cela s'applique uniquement aux applications qui ne sont pas également installées dans la guilde).", + "SET_VOICE_CHANNEL_STATUS": "Définir le statut du canal vocal" }, "readableNames": { "ADD_REACTIONS": "Ajouter des réactions", @@ -816,7 +819,8 @@ "VIEW_AUDIT_LOG": "Afficher le journal d'audit", "VIEW_CHANNEL": "Voir les canaux", "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Consulter les analyses de monétisation des créateurs", - "VIEW_GUILD_INSIGHTS": "Voir les informations sur la guilde" + "VIEW_GUILD_INSIGHTS": "Voir les informations sur la guilde", + "SET_VOICE_CHANNEL_STATUS": "Définir le statut du canal vocal" } }, "pinMessage": "Épingler le message", @@ -824,12 +828,13 @@ "bio": "À propos de moi :", "joined": "Compte créé : $1", "joinedMember": "A rejoint $1 : $2", - "mut": "Guildes en commun", + "mut": "Guildes mutuelles ($1)", "mutFriends": "Amis en commun", "permInfo": "Permissions", "userInfo": "Informations sur l’utilisateur", "notes": "Notes d'utilisateur" }, + "messageNotLoad": "Échec du chargement des messages", "profileColor": "Couleur du profil", "pronouns": "Pronoms :", "readableName": "Français", diff --git a/translations/it.json b/translations/it.json index 2cc88c3..ad5326f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -25,6 +25,12 @@ "whatelse": "Cos'altro pensi che dovrebbe succedere?", "whereever": "Ovunque questo sia" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "onboarding": { "name": "Inserimento", "addChannel": "Aggiungi canale", @@ -123,6 +129,7 @@ "cancel": "Annulla", "channel": { "SlowmodeCool": "Tempo di attesa slowmode: $1", + "copyURL": "Copia l'URL del canale", "TimeOutCool": "In time-out fino: $1", "allowIcons": "Consenti di mettere icone del canale personalizzati", "announcement": "Annunci", diff --git a/translations/ko.json b/translations/ko.json index e2ad31f..a0772c5 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -24,6 +24,12 @@ "whatelse": "어떻게 되어야 한다고 생각시나요?", "whereever": "이곳이 무엇이든" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "onboarding": { "name": "온보딩", "disable": "온보딩 비활성화", @@ -156,7 +162,7 @@ "text": "텍스트", "timedOutUntil": "다음 시간까지 타임아웃: $1", "topic:": "주제:", - "typebox": "$1에서의 메시지", + "typebox": "# $1에서의 메시지", "unmute": "채널 알림 표시", "voice": "음성", "deleteThread": "스레드 삭제", @@ -806,7 +812,7 @@ "bio": "내 정보:", "joined": "계정 생성일: $1", "joinedMember": "가입일 $1: $2", - "mut": "상호 길드", + "mut": "상호 길드 ($1)", "mutFriends": "상호 친구", "permInfo": "권한", "userInfo": "사용자 정보", diff --git a/translations/lb.json b/translations/lb.json index 2ead5b2..983f0e2 100644 --- a/translations/lb.json +++ b/translations/lb.json @@ -11,6 +11,12 @@ "home": "Haaptsäit", "login": "Aloggen" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "onboarding": { "addChannel": "Kanal derbäisetzen", "channel": "Kanal:", @@ -19,6 +25,7 @@ }, "copyMedia": "Medien-URL kopéieren", "DMs": { + "copyURL": "DM-URL kopéieren", "markRead": "Als gelies markéieren" }, "ok": "OK", @@ -54,6 +61,7 @@ "botSettings": "Bot-Astellungen", "cancel": "Ofbriechen", "channel": { + "copyURL": "Kanal-URL kopéieren", "announcement": "Ukënnegungen", "copyId": "Kanal-ID kopéieren", "createCatagory": "Kategorie uleeën", @@ -316,6 +324,7 @@ "bio": "Iwwer mech:", "userInfo": "Benotzerinformatiounen" }, + "messageNotLoad": "Messagë konnten net geluede ginn", "profileColor": "Profilfaarf", "pronouns": "Pronomen:", "readableName": "Lëtzebuergesch", @@ -404,7 +413,7 @@ "since": "Member zanter: $1", "sure": "Sidd Dir sécher?" }, - "uploadFilesText": "Lued Är Fichieren hei erop!", + "uploadFilesText": "Luet Är Fichieren hei erop!", "useTemplate": "$1 als Schabloun benotzen", "useTemplateButton": "Schabloun benotzen", "user": { diff --git a/translations/nl.json b/translations/nl.json index c3e5777..0d8cea5 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -21,6 +21,12 @@ "whatelse": "Wat denkt u dat er nog meer moet gebeuren?", "whereever": "Waar dit ook is" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "onboarding": { "name": "Inwerken", "disable": "Inwerken uitschakelen", @@ -35,6 +41,7 @@ "DMs": { "add": "Iemand aan dit directe bericht toevoegen", "close": "Directe bericht sluiten", + "copyURL": "DM-url kopiëren", "copyId": "Directe bericht-ID kopiëren", "markRead": "Markeren als gelezen", "name": "Directe berichten" @@ -126,6 +133,7 @@ "cancel": "Annuleren", "channel": { "SlowmodeCool": "Afkoeltijd voor langzame modus: $1", + "copyURL": "Kanaal-url kopiëren", "TimeOutCool": "Time-out bereikt na: $1", "allowIcons": "Aangepaste kanaalpictogrammen toestaan", "announcement": "Aankondigingen", @@ -153,7 +161,7 @@ "text": "Tekst", "timedOutUntil": "Time-out bereikt na: $1", "topic:": "Onderwerp:", - "typebox": "Bericht in $1", + "typebox": "Bericht in # $1", "unmute": "Dempen kanaal opheffen", "voice": "Spraak", "deleteThread": "Onderwerp verwijderen", @@ -742,7 +750,8 @@ "REQUEST_TO_SPEAK": "Laat rolleden een spreekverzoek indienen in de podiumkanalen.", "USE_EMBEDDED_ACTIVITIES": "Hiermee kunnen rolleden ingebedde activiteiten gebruiken.", "USE_APPLICATION_COMMANDS": "Hiermee kunnen rolleden toepassingsopdrachten gebruiken.", - "USE_EXTERNAL_APPS": "Hiermee kunnen rolleden hun toepassingenreacties openbaar in het kanaal weergeven. Indien uitgeschakeld, kunnen gebruikers hun apps nog steeds gebruiken, maar zijn de reacties alleen voor henzelf zichtbaar. Dit geldt alleen voor apps die niet ook in het gilde zijn geïnstalleerd." + "USE_EXTERNAL_APPS": "Hiermee kunnen rolleden hun toepassingenreacties openbaar in het kanaal weergeven. Indien uitgeschakeld, kunnen gebruikers hun apps nog steeds gebruiken, maar zijn de reacties alleen voor henzelf zichtbaar. Dit geldt alleen voor apps die niet ook in het gilde zijn geïnstalleerd.", + "SET_VOICE_CHANNEL_STATUS": "Spraakkanaalstatus instellen" }, "readableNames": { "ADD_REACTIONS": "Reacties toevoegen", @@ -795,7 +804,8 @@ "VIEW_AUDIT_LOG": "Inspectielogboek bekijken", "VIEW_CHANNEL": "Kanalen bekijken", "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Monetisatie-analyses voor makers bekijken", - "VIEW_GUILD_INSIGHTS": "Gilde-inzichten bekijken" + "VIEW_GUILD_INSIGHTS": "Gilde-inzichten bekijken", + "SET_VOICE_CHANNEL_STATUS": "Spraakkanaalstatus instellen" } }, "pinMessage": "Bericht vastzetten", @@ -803,12 +813,13 @@ "bio": "Over mij:", "joined": "Account aangemaakt: $1", "joinedMember": "Toegetreden tot $1: $2", - "mut": "Gedeelde gilden", + "mut": "Gedeelde gilden ($1)", "mutFriends": "Wederzijdse vrienden", "permInfo": "Rechten", "userInfo": "Gebruikersinfo", "notes": "Gebruikersopmerkingen" }, + "messageNotLoad": "Berichten konden niet worden geladen", "profileColor": "Profielkleur", "pronouns": "Voornaamwoorden:", "readableName": "Nederlands", diff --git a/translations/zh-hans.json b/translations/zh-hans.json index 785f913..da9421d 100644 --- a/translations/zh-hans.json +++ b/translations/zh-hans.json @@ -29,6 +29,12 @@ "whatelse": "您认为应该还会发生什么事?", "whereever": "无论这在哪里" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "onboarding": { "name": "入门", "disable": "禁用入门", @@ -43,6 +49,7 @@ "DMs": { "add": "将某人添加到此私信", "close": "关闭私信", + "copyURL": "复制私信 URL", "copyId": "复制私信 ID", "markRead": "标记为已读", "name": "私信" @@ -134,6 +141,7 @@ "cancel": "取消", "channel": { "SlowmodeCool": "慢速模式冷却时间:$1", + "copyURL": "复制频道 URL", "TimeOutCool": "超时至:$1", "allowIcons": "允许自定义频道图标", "announcement": "公告", @@ -813,12 +821,13 @@ "bio": "关于我:", "joined": "账户已创建: $1", "joinedMember": "已加入$1:$2", - "mut": "互助公会", + "mut": "互助公会($1)", "mutFriends": "共同好友", "permInfo": "权限", "userInfo": "用户信息", "notes": "用户笔记" }, + "messageNotLoad": "消息加载失败", "profileColor": "个人档案色", "pronouns": "称谓:", "readableName": "简体中文", From c2a1c8fd98330cea6329e2d0a5b92cb456720148 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Mon, 3 Aug 2026 19:40:33 -0500 Subject: [PATCH 31/66] firefox MD fixes --- src/webpage/markdown.ts | 63 +++++++++++++++++++++++++---------------- src/webpage/message.ts | 5 ++-- src/webpage/typeBox.ts | 14 +++++++-- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index f826ca7..a05b5fa 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -6,6 +6,7 @@ import {I18n} from "./i18n.js"; import {Dialog} from "./settings.js"; import {Contextmenu} from "./contextmenu.js"; import {highlight} from "./highlighter/index.js"; +import {TypeBox} from "./typeBox.js"; const linkMenu = new Contextmenu("copyLink", true); linkMenu.addButton( () => I18n.copyRegLink(), @@ -14,7 +15,7 @@ linkMenu.addButton( }, {group: "copyLink"}, ); -const isFirefox = navigator.userAgent.toLowerCase().includes("firefox"); +const isFirefox = Error.prototype.stack === ""; class MarkDown { static emoji?: typeof Emoji; txt: string[]; @@ -351,19 +352,20 @@ class MarkDown { if (stdsize) { build = build.replaceAll("\n", ""); } - if (find === count) { + if (find === count || (keep && count === 3)) { appendcurrent(); i = j; if (keep) { build += "`".repeat(find); } + if (build.endsWith("\n")) build += "\n"; if (count !== 3 && !stdsize) { const samp = document.createElement("samp"); samp.textContent = build; span.appendChild(samp); } else { const pre = document.createElement("pre"); - if (build.at(-1) === "\n") { + if (build.at(-1) === "\n" && !isFirefox) { build = build.substring(0, build.length - 1); } if (txt[i] === "\n") { @@ -974,14 +976,20 @@ class MarkDown { } appendcurrent(); const last = getCurLast(); - + function addSpacer() { + const s = document.createElement("span"); + s.style.setProperty("white-space", "pre"); + s.textContent = "​"; + s.setAttribute("real", ""); + span.append(s); + } if ( last && - last instanceof Text && - last.textContent === "\n" && - Error.prototype.stack === "" && - !isFirefox + isFirefox && + (last instanceof Text || last instanceof HTMLSpanElement) && + last.textContent === "\n" ) { + addSpacer(); span.append(current); } if ( @@ -1052,7 +1060,8 @@ class MarkDown { box.addEventListener("keydown", (_) => { if (_.isComposing) return; if (Error.prototype.stack !== "") return; - if (_.key === "Enter") { + if (_.key === "Enter" && !TypeBox.inPre() && !_.shiftKey) { + return; const selection = window.getSelection() as Selection; if (!selection) return; const range = selection.getRangeAt(0); @@ -1083,28 +1092,29 @@ class MarkDown { const content = this.rawString; if (content) { let txti = text; - if (trim) { + if (trim && TypeBox.inPre()) { txti = txti.replace(/\n$/, ""); } - const [_first, end] = content.split(txti); - console.log([txti, txt, end]); + const [first, ...ends] = content.split(txti); + const end = ends.join(txti); if (rstr) { const tw = text.split(rstr); tw.pop(); text = tw.join(""); } - const boxText = txti + txt + (end ?? ""); - box.textContent = boxText; + const boxText = first + txti + txt + (end ?? ""); + box.innerHTML = ""; + this.txt = boxText.split(""); + box.append(this.makeHTML({keep: true})); const len = txti.length + txt.length; text = boxText; - this.txt = text.split(""); this.boxupdate(len, false, 0); - console.log(this.rawString); } else { - box.textContent = txt; + this.txt = txt.split(""); + box.innerHTML = ""; + box.append(this.makeHTML({keep: true})); text = txt; - this.txt = text.split(""); this.boxupdate(txt.length, false, 0); } }; @@ -1114,7 +1124,7 @@ class MarkDown { gatherBoxContents(_.key === "Backspace"); }; box.onkeydown = (_) => { - if (isFirefox && _.key === "Enter" && !text.endsWith("\n")) { + if (isFirefox && _.key === "Enter" && (TypeBox.inPre() || _.shiftKey)) { _.preventDefault(); _.stopImmediatePropagation(); @@ -1191,6 +1201,7 @@ class MarkDown { html.childNodes[0].childNodes.length === 1 && html.childNodes[0].childNodes[0]; //console.log(box.cloneNode(true), html.cloneNode(true)); + if (isFirefox) allowLazy = false; //TODO this may be slow, may want to check in on this in the future if it is if ((!box.hasChildNodes() || html.isEqualNode(Array.from(box.childNodes)[0])) && allowLazy) { //console.log("no replace needed"); @@ -1233,6 +1244,11 @@ class MarkDown { if (thing instanceof Text) { const text = thing.textContent; build += text; + if (element.tagName.toLowerCase() === "pre") { + if (build.endsWith("\n") && isFirefox) { + build = build.replace(/\n$/, ""); + } + } continue; } @@ -1431,13 +1447,13 @@ function saveCaretPosition( build += node.textContent; } } else { - //console.error(node,"This shouldn't happen"); + //console.error(node, "This shouldn't happen"); } } } crawlForText(context); if (baseString === "\n") { - build += baseString; + //build += baseString; } text = build; len += build.length; @@ -1447,15 +1463,14 @@ function saveCaretPosition( len = Math.min(len, txtLengthFunc(context).length); len += offset; - return function restore(backspace = false) { + return function restore(_backspace = false) { if (!selection) return; const pos = getTextNodeAtPosition(context, len, txtLengthFunc); if ( pos.node instanceof Text && pos.node.textContent === "\n" && pos.node.nextSibling && - Error.prototype.stack === "" && - !backspace + isFirefox ) { if (pos.node.nextSibling instanceof Text && pos.node.nextSibling.textContent === "\n") { pos.position = 1; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 13b6cc4..b17d0de 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -28,6 +28,7 @@ import {ImagesDisplay} from "./disimg"; import {ReportMenu} from "./reporting/report.js"; import {getDeveloperSettings} from "./utils/storage/devSettings.js"; import {getPreferences} from "./utils/storage/userPreferences.js"; +import {TypeBox} from "./typeBox.js"; class Message extends SnowFlake { static contextmenu = new Contextmenu("message menu"); stickers!: Sticker[]; @@ -1017,7 +1018,7 @@ class Message extends SnowFlake { area.append(md.makeHTML()); area.addEventListener("keyup", (event) => { if (this.localuser.keyup(event)) return; - if (event.key === "Enter" && !event.shiftKey) { + if (event.key === "Enter" && !event.shiftKey && !TypeBox.inPre()) { this.edit(MarkDown.gatherBoxText(area)); this.channel.editing = null; this.generateMessage(); @@ -1025,7 +1026,7 @@ class Message extends SnowFlake { }); area.addEventListener("keydown", (event) => { this.localuser.keydown(event); - if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); + if (event.key === "Enter" && !event.shiftKey && !TypeBox.inPre()) event.preventDefault(); if (event.key === "Escape") { this.channel.editing = null; this.generateMessage(); diff --git a/src/webpage/typeBox.ts b/src/webpage/typeBox.ts index 49f7abe..16d1b8c 100644 --- a/src/webpage/typeBox.ts +++ b/src/webpage/typeBox.ts @@ -33,7 +33,7 @@ export class TypeBox { this.box.addEventListener("keydown", (event) => { if (event.isComposing) return; this.localuser?.keydown(event); - if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { + if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600 && !TypeBox.inPre()) { event.preventDefault(); event.stopImmediatePropagation(); } @@ -268,6 +268,16 @@ export class TypeBox { }; this.localuser = l; } + static inPre() { + const selection = window.getSelection(); + if (!selection) return false; + let node = selection.anchorNode; + while (node) { + if (node instanceof HTMLPreElement) return true; + node = node.parentElement; + } + return false; + } private static async handleEnter(event: KeyboardEvent): Promise { if (event.isComposing) return; if (event.key === "Escape" && (this.files.length || this.localuser?.focusChannel?.replyingto)) { @@ -296,7 +306,7 @@ export class TypeBox { } channel.typingstart(); - if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) { + if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600 && !this.inPre()) { event.preventDefault(); await this.sendMessage(channel, content); } From e4df86158aaa1e1064756935bd3fd581590b3059 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 00:53:17 +0000 Subject: [PATCH 32/66] feat: gallery size images equally Signed-off-by: lizzie --- src/webpage/embed.ts | 2 -- src/webpage/file.ts | 15 +++++++++------ src/webpage/message.ts | 3 ++- src/webpage/style.css | 32 ++++++++++++++++++++++---------- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index 3b2e32c..ef3857d 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -210,7 +210,6 @@ class Embed { const div = document.createElement("div"); div.classList.add("messageimgdiv"); const img = createImg(this.json.thumbnail.proxy_url); - img.classList.add("messageimg"); img.onclick = () => { const full = new ImagesDisplay([ new File( @@ -442,7 +441,6 @@ class Embed { video.controls = true; video.preload = "metadata"; video.src = videoUrl; - video.classList.add("messageimg"); if (this.json.video?.width) { let scale = 1; const max = 96 * 3; diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 8f91e18..2bfeb6a 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -44,7 +44,7 @@ class File { this.content_type = fileJSON.content_type; this.size = fileJSON.size; } - getHTML(temp: boolean = false, fullScreen = false, OSpoiler = false, max = 96 * 3): HTMLElement { + getHTML(temp: boolean = false, fullScreen = false, OSpoiler = false, max = 96 * 3, gallery = false): HTMLElement { function makeSpoilerHTML(): HTMLElement { const spoil = document.createElement("div"); spoil.classList.add("fSpoil"); @@ -80,8 +80,10 @@ class File { img.height = this.height; } if (!fullScreen) { - img.classList.add("messageimg"); div.classList.add("messageimgdiv"); + if (gallery) { + div.classList.add("messagegallerydiv"); + } img.onclick = () => { if (this.owner) { const full = new ImagesDisplay( @@ -106,12 +108,13 @@ class File { img.setSrcs(src); }); div.append(img); - if (this.width && !fullScreen) { + // Non-gallery images are sized according to their actual size + if (!gallery && this.width && !fullScreen) { img.style.width = div.style.width = this.width + "px"; img.style.height = div.style.height = this.height + "px"; - } else if (!fullScreen) { - img.style.maxWidth = div.style.maxWidth = 96 * 3 + "px"; - img.style.maxHeight = div.style.maxHeight = 96 * 3 + "px"; + } else if (!gallery && !fullScreen) { + img.style.maxWidth = div.style.maxWidth = max + "px"; + img.style.maxHeight = div.style.maxHeight = max + "px"; } img.isAnimated().then((animated) => { if (!animated || !this.owner || fullScreen) return; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index b17d0de..c891b56 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1083,8 +1083,9 @@ class Message extends SnowFlake { if (this.attachments.length) { const attach = document.createElement("div"); attach.classList.add("flexltr", "attachments"); + const isGallery = this.attachments.length > 1; for (const thing of this.attachments) { - attach.appendChild(thing.getHTML()); + attach.appendChild(thing.getHTML(false, false, false, 96 * 3, isGallery)); } messagedwrap.appendChild(attach); } diff --git a/src/webpage/style.css b/src/webpage/style.css index f5444db..cbea637 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2505,16 +2505,13 @@ span.instanceStatus { overflow: clip; width: fit-content; height: fit-content; -} -.messageimg { - height: 100%; - width: 100%; - object-fit: contain; - user-select: none; - cursor: pointer; -} -.attachments .messageimg { - border-radius: 4px; + img, div { + height: 100%; + width: 100%; + object-fit: contain; + user-select: none; + cursor: pointer; + } } #replybox { height: 32px; @@ -3174,6 +3171,21 @@ span .quote:last-of-type .quoteline { max-height: 25%; max-width: 50%; } + .messageimgdiv { + img, video { + border-radius: 4px; + } + } + .messagegallerydiv { + object-fit: fill; + height: 200px; + max-width: 50%; + overflow: hidden; + img, video { + height: 100%; + object-fit: cover; + } + } } .embed { max-width: 400px; From 00b59f68dfcd34428cc919bd8747851f509038f1 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Mon, 3 Aug 2026 20:00:24 -0500 Subject: [PATCH 33/66] delete unreachable code --- src/webpage/markdown.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index a05b5fa..39a6986 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -1060,18 +1060,6 @@ class MarkDown { box.addEventListener("keydown", (_) => { if (_.isComposing) return; if (Error.prototype.stack !== "") return; - if (_.key === "Enter" && !TypeBox.inPre() && !_.shiftKey) { - return; - const selection = window.getSelection() as Selection; - if (!selection) return; - const range = selection.getRangeAt(0); - const node = new Text("\n"); - range.insertNode(node); - const g = node.nextSibling; - if (g) range.setStart(g, 0); - _.preventDefault(); - return; - } }); let prevcontent = ""; const gatherBoxContents = (isBackSpace: boolean) => { From 3352716320874b801a7e2ff6186a781dafd36d5d Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 01:08:09 +0000 Subject: [PATCH 34/66] feat: more contextual notifications Signed-off-by: lizzie --- src/webpage/notificationHandler.ts | 11 +++++++++++ translations/en.json | 3 +++ translations/qqq.json | 3 +++ 3 files changed, 17 insertions(+) diff --git a/src/webpage/notificationHandler.ts b/src/webpage/notificationHandler.ts index b7496ee..5a33c1f 100644 --- a/src/webpage/notificationHandler.ts +++ b/src/webpage/notificationHandler.ts @@ -20,6 +20,17 @@ export class NotificationHandler { noticontent ||= message.embeds[0]?.json.title; noticontent ||= message.content.textContent; } + if (message.attachments.length > 0) { + noticontent ||= I18n.sentAttachment(); + } + if (message.poll) { + noticontent ||= I18n.sentPoll(); + } + if (message.stickers.length > 0) { + noticontent ||= I18n.sentSticker(); + } + // exhausted all options, sorry, we just send the raw thing! + noticontent ||= message.content.rawString; noticontent ||= I18n.blankMessage(); const image = message.getimages()[0]; diff --git a/translations/en.json b/translations/en.json index 11b7273..4c2cadf 100644 --- a/translations/en.json +++ b/translations/en.json @@ -117,6 +117,9 @@ }, "bio": "Bio:", "blankMessage": "Blank message", + "sentAttachment": "Sent an attachment", + "sentPoll": "Sent a poll", + "sentSticker": "Sent a sticker", "blog": { "blog": "Blog", "blogUpdates": "Get blog updates when you open the app:", diff --git a/translations/qqq.json b/translations/qqq.json index 9eb1ea0..5c7334b 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -119,6 +119,9 @@ }, "bio": "Label for the bio/about-me text field in user settings.", "blankMessage": "Placeholder text shown when a message has no content.", + "sentAttachment": "Notification message when someone sends an attachment", + "sentPoll": "Notification message when someone sends a poll", + "sentSticker": "Notification message when someone sends a sticker", "blog": { "blog": "Settings section title for the blog feature.", "blogUpdates": "Label for the toggle to enable/disable blog update notifications.", From 93e2845b5dbd9fa634aca1ea4c70021e3f11098b Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 01:19:10 +0000 Subject: [PATCH 35/66] better reaction min width --- src/webpage/style.css | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 0e194bf..1fb1826 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3149,8 +3149,8 @@ span .quote:last-of-type .quoteline { user-select: none; } .reaction { - min-width: 2em; - min-height: 2em; + min-width: 1.25em; + min-height: 1.25em; padding: 3px 4px; margin-top: 4px; background: var(--reaction-bg); @@ -3161,10 +3161,7 @@ span .quote:last-of-type .quoteline { align-items: center; cursor: pointer; gap: 4px; - .smallemoji { - max-width: 1.5em; - max-height: 1.55em; - } + font-size: 1.25em; } .reaction p { line-height: 1em; From de77b72ab04f6ee0adb2aee5fa8e2d96aa383e54 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 02:03:08 +0000 Subject: [PATCH 36/66] fix: join button Signed-off-by: lizzie --- src/webpage/typeBox.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/webpage/typeBox.ts b/src/webpage/typeBox.ts index 16d1b8c..6c6ed85 100644 --- a/src/webpage/typeBox.ts +++ b/src/webpage/typeBox.ts @@ -370,4 +370,7 @@ export class TypeBox { this.nonceMap.delete(channel.id); } } -TypeBox.init(); + +if (TypeBox.box) { + TypeBox.init(); +} From 717f215c18c24aadf6de2d615c0af5b7caf40bc3 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 03:29:57 +0000 Subject: [PATCH 37/66] fix: bottom cutoff of member list Signed-off-by: lizzie --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 1fb1826..2d0d4ff 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3333,7 +3333,7 @@ img.bigembedimg { /* Sidebar */ #sideContainDiv { - padding: 16px 8px; + padding: 16px 8px 0px 8px; display: none; flex: none; width: 240px; From e24e747fea7f0ca7df687dc1941fff9a6d480098 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 03:34:48 +0000 Subject: [PATCH 38/66] feat: more visible mentions on white mode Signed-off-by: lizzie --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 1fb1826..ca25edb 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3102,7 +3102,7 @@ span .quote:last-of-type .quoteline { aspect-ratio: 1.9; } .mentionMD { - color: transparent; + color: color-mix(in srgb, var(--mention) 50%, var(--primary-text)); background-clip: text; padding: 1px 2px; border-radius: 4px; From de8ae7d6804e84ac607653cde57fa54f2288f77f Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 03:42:52 +0000 Subject: [PATCH 39/66] fix: gif picker on white mode --- src/webpage/style.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 1fb1826..7b303b5 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5017,14 +5017,15 @@ fieldset input[type="radio"] { .gifPreviewBox { position: relative; width: 2in; + height: 1in; margin-bottom: 10px; border-radius: 7px; overflow: hidden; cursor: pointer; img { - width: 2in; - height: 1in; + width: 100%; + height: 100%; object-fit: cover; } span { @@ -5036,8 +5037,9 @@ fieldset input[type="radio"] { display: inline-flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--card-bg) 60%, transparent); font-weight: bold; + color: white; + background: color-mix(in srgb, black 25%, transparent); } } .gifbox { From 930911efd72ef89f3ba32f6eed3d4245c3c5e39f Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 03:46:02 +0000 Subject: [PATCH 40/66] keep same blackness colour --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 7b303b5..27bfa45 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5039,7 +5039,7 @@ fieldset input[type="radio"] { justify-content: center; font-weight: bold; color: white; - background: color-mix(in srgb, black 25%, transparent); + background: color-mix(in srgb, black 50%, transparent); } } .gifbox { From 551e40b9e84f159f09d08da25743a428df0b4be4 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 04:05:16 +0000 Subject: [PATCH 41/66] feat: make 'BOT' and 'WEBHOOK' be in white letters always Signed-off-by: lizzie --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 1fb1826..0c0dd1b 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2881,7 +2881,7 @@ span.instanceStatus { background: color-mix(in srgb, var(--accent-color) 75%, transparent) !important; font-size: 0.75em !important; font-weight: bold; - color: var(--primary-text-prominent) !important; + color: white !important; border-radius: 4px; flex: none; width: fit-content; From 259edd7697831bed1426f14253d1fccdaa09e57c Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 04:07:48 +0000 Subject: [PATCH 42/66] vert align --- src/webpage/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 0c0dd1b..8e1728b 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2887,6 +2887,7 @@ span.instanceStatus { width: fit-content; background-clip: border-box !important; align-self: center; + vertical-align: center; } .membererror { display: inline-block; From 6d593a645591391d7011f828ae2df951b424a706 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 04:25:08 +0000 Subject: [PATCH 43/66] use userbg for text, oops! --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index ca25edb..9898f5d 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3102,11 +3102,11 @@ span .quote:last-of-type .quoteline { aspect-ratio: 1.9; } .mentionMD { - color: color-mix(in srgb, var(--mention) 50%, var(--primary-text)); background-clip: text; padding: 1px 2px; border-radius: 4px; cursor: pointer; + color: color-mix(in srgb, var(--userbg) 50%, var(--primary-text)); background-image: var(--userbg, linear-gradient(var(--primary-text), var(--primary-text))); position: relative; z-index: 1; From 0f32bbba29a5d8ac23a914f11a449ec4600239ae Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 04:27:58 +0000 Subject: [PATCH 44/66] increase to 75% --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 9898f5d..22f6571 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3106,7 +3106,7 @@ span .quote:last-of-type .quoteline { padding: 1px 2px; border-radius: 4px; cursor: pointer; - color: color-mix(in srgb, var(--userbg) 50%, var(--primary-text)); + color: color-mix(in srgb, var(--userbg) 75%, var(--primary-text)); background-image: var(--userbg, linear-gradient(var(--primary-text), var(--primary-text))); position: relative; z-index: 1; From ff470e2834c244806934d7301a137f18a7b917ce Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 04:36:55 +0000 Subject: [PATCH 45/66] feat: add bash and json highlight Signed-off-by: lizzie --- src/webpage/highlighter/clike/langs.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json index 40b8743..81df893 100644 --- a/src/webpage/highlighter/clike/langs.json +++ b/src/webpage/highlighter/clike/langs.json @@ -7,6 +7,10 @@ "multilineSlashComments": true, "JSLikeTemplateStrings": true }, +{ + "names":["json"], + "keywords":["true","false","null"] +}, { "names":["h","c","hc","c23","c89","ansic","ansi-c","khr-c","khrc"], "keywords":["auto", "break", "case", "char", "continue", "do", "default", "const", "double", "else", "enum", "extern", "for", "if", "goto", "float", "int", "long", "register", "return", "signed", "static", "sizeof", "short", "struct", "switch", "typedef", "union", "void", "while", "volatile", "unsigned"], @@ -85,5 +89,13 @@ "doubleSlashComments": true, "multilineSlashComments": true, "multiLine":"\"\"\"" +}, +{ + "names":["bash","sh","ksh","csh","shell"], + "keywords":["if","fi","else","then","while","do","for","continue","done","break","case","esac","in","local","return","declare","EOF","readonly","true","false"], + "firstLineShebang":true, + "hashComments": true, + "doubleSlashComments": false, + "multilineSlashComments": false } ] From af5794622c920d0a64ad7e9a525573a82b4058fc Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 14:46:29 +0000 Subject: [PATCH 46/66] not json --- src/webpage/highlighter/clike/langs.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json index 81df893..1624a79 100644 --- a/src/webpage/highlighter/clike/langs.json +++ b/src/webpage/highlighter/clike/langs.json @@ -7,10 +7,6 @@ "multilineSlashComments": true, "JSLikeTemplateStrings": true }, -{ - "names":["json"], - "keywords":["true","false","null"] -}, { "names":["h","c","hc","c23","c89","ansic","ansi-c","khr-c","khrc"], "keywords":["auto", "break", "case", "char", "continue", "do", "default", "const", "double", "else", "enum", "extern", "for", "if", "goto", "float", "int", "long", "register", "return", "signed", "static", "sizeof", "short", "struct", "switch", "typedef", "union", "void", "while", "volatile", "unsigned"], From c1072731f77471d664003e850ccf98bb7800c235 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 4 Aug 2026 17:27:24 +0000 Subject: [PATCH 47/66] feat: stylized scrollbar Signed-off-by: lizzie --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 1fb1826..cf37ded 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1,3 +1,6 @@ +* { + scrollbar-color: var(--button-bg) var(--primary-bg); +} /* General */ body { height: 100%; From cac9a7740ebf93b1ae5cb9cbaab8e3965ea52221 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Tue, 4 Aug 2026 13:07:44 -0500 Subject: [PATCH 48/66] delete unused class Will redo something similar later --- src/webpage/utils/utils.ts | 81 -------------------------------------- 1 file changed, 81 deletions(-) diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index 837b4e7..c714636 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -290,87 +290,6 @@ export function adduser(user: typeof Specialuser.prototype.json): Specialuser { localStorage.setItem("userinfos", JSON.stringify(info)); return suser; } -class Directory { - static home = this.createHome(); - handle: FileSystemDirectoryHandle; - writeWorker?: Worker; - private constructor(handle: FileSystemDirectoryHandle) { - this.handle = handle; - } - static async createHome(): Promise { - navigator.storage.persist(); - const home = new Directory(await navigator.storage.getDirectory()); - return home; - } - async *getAllInDir() { - for await (const [name, handle] of this.handle.entries()) { - if (handle instanceof FileSystemDirectoryHandle) { - yield [name, new Directory(handle)] as [string, Directory]; - } else if (handle instanceof FileSystemFileHandle) { - yield [name, await handle.getFile()] as [string, File]; - } else { - console.log(handle, "oops :3"); - } - } - console.log("done"); - } - async getRawFileHandler(name: string) { - return await this.handle.getFileHandle(name); - } - async getRawFile(name: string) { - try { - return await (await this.handle.getFileHandle(name)).getFile(); - } catch { - return undefined; - } - } - async getString(name: string): Promise { - try { - return await (await this.getRawFile(name))!.text(); - } catch { - return undefined; - } - } - initWorker() { - if (this.writeWorker) return this.writeWorker; - this.writeWorker = new Worker("/utils/dirrWorker.js"); - this.writeWorker.onmessage = (event) => { - const res = this.wMap.get(event.data[0]); - this.wMap.delete(event.data[0]); - if (!res) throw new Error("Res is not defined here somehow"); - res(event.data[1]); - }; - return this.writeWorker; - } - wMap = new Map void>(); - async setStringWorker(name: FileSystemFileHandle, value: ArrayBuffer) { - const worker = this.initWorker(); - const random = Math.random(); - worker.postMessage([name, value, random]); - return new Promise((res) => { - this.wMap.set(random, res); - }); - } - async setString(name: string, value: string): Promise { - const file = await this.handle.getFileHandle(name, {create: true}); - const contents = new TextEncoder().encode(value); - - if (file.createWritable as unknown) { - const stream = await file.createWritable({keepExistingData: false}); - await stream.write(contents); - await stream.close(); - return true; - } else { - //Curse you webkit! - return await this.setStringWorker(file, contents.buffer as ArrayBuffer); - } - } - async getDir(name: string) { - return new Directory(await this.handle.getDirectoryHandle(name, {create: true})); - } -} - -export {Directory}; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) || From fc08e263ce7cb039559cf40c7324de4a1c9b4e2b Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Tue, 4 Aug 2026 17:08:28 -0500 Subject: [PATCH 49/66] slight change --- src/webpage/style.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 8006d24..29fc627 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1,6 +1,3 @@ -* { - scrollbar-color: var(--button-bg) var(--primary-bg); -} /* General */ body { height: 100%; @@ -10,6 +7,7 @@ body { color: var(--primary-text); overflow: hidden; /* avoid "bounce" */ -webkit-text-size-adjust: 100%; + scrollbar-color: var(--button-bg) var(--primary-bg); } #page { height: 100svh; From 7e3bd18a8d063bd7412b04bfb4fb92d826b3a4df Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Tue, 4 Aug 2026 17:26:59 -0500 Subject: [PATCH 50/66] group DM fix --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index c1fbe87..214e145 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5287,7 +5287,7 @@ img.error::after { background: transparent; width: 48px; height: 48px; - img { + .pfpDiv { width: 24px; height: 24px; &:nth-child(1) { From 5bb7ec304045dae18dea73d2c2f0f699338682f1 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 5 Aug 2026 02:21:21 +0000 Subject: [PATCH 51/66] feat: make white mode text darker Signed-off-by: lizzie --- src/webpage/themes.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/themes.css b/src/webpage/themes.css index 16eb6b3..0dfdf51 100644 --- a/src/webpage/themes.css +++ b/src/webpage/themes.css @@ -73,7 +73,7 @@ --primary-button-bg: color-mix(in srgb, #000000 10%, var(--accent-color)); --primary-bg: #fefefe; --primary-hover: #f6f6f9; - --primary-text: #4b4b59; + --primary-text: black; --primary-text-soft: #656575; --secondary-bg: #e0e0ea; From 58c44997594b06b84122a8df1341b6e44a311ad4 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 5 Aug 2026 15:05:54 +0000 Subject: [PATCH 52/66] feat: pfp banner aligned to profile Signed-off-by: lizzie --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 214e145..666dcaa 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3526,6 +3526,9 @@ img.bigembedimg { z-index: 3; display: flex; flex-direction: column; + .banner { + height: 64px; + } .infosection { background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent); } From a7e6ec28c3e0a5462aa3f1ea3a51207c30f6c19d Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 5 Aug 2026 12:58:43 -0500 Subject: [PATCH 53/66] fix z-index bug --- src/webpage/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 214e145..52ecbcb 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3523,9 +3523,10 @@ img.bigembedimg { border-radius: 8px; overflow: hidden; align-items: flex-start; - z-index: 3; + z-index: 4; display: flex; flex-direction: column; + .infosection { background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent); } From 60c22827735ad7172594830bb764c5c271ef2aa2 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 5 Aug 2026 13:58:32 -0500 Subject: [PATCH 54/66] reverse tri-select order --- src/webpage/style.css | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 52ecbcb..80a918a 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -4523,32 +4523,34 @@ fieldset input[type="radio"] { position: relative; padding: 0px; cursor: pointer; + flex-direction: row-reverse; + &::after { content: ""; background: var(--secondary-bg); - transition: left 0.2s; + transition: right 0.2s; border-radius: 4px; width: 18px; height: 18px; display: block; top: 3px; - left: 2px; + right: 2px; position: absolute; } - :first-child { + :last-child { border-radius: 5px 0px 0px 5px; } - :last-child { + :first-child { border-radius: 0px 5px 5px 0px; } &:has(:first-child.selected)::after { - left: 3px; + right: 3px; } &:has(:nth-child(2).selected)::after { - left: 27px; + right: 27px; } &:has(:nth-child(3).selected)::after { - left: 51px; + right: 51px; } } .tritoggle > .triOpt { From cdedf50b86aa4d992705d71564f466abb440745e Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 5 Aug 2026 14:47:15 -0500 Subject: [PATCH 55/66] fix the hover bugs --- src/webpage/hover.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts index 490289a..017ff37 100644 --- a/src/webpage/hover.ts +++ b/src/webpage/hover.ts @@ -68,9 +68,13 @@ class Hover { this.elm2.remove(); }); } + prevDiv = new WeakRef(document.createElement("div")); async makeHover(elm: HTMLElement) { + const prev = this.prevDiv.deref(); + if (prev) prev.remove(); if (!document.contains(elm)) return document.createElement("div"); const div = document.createElement("div"); + this.prevDiv = new WeakRef(div); if (this.customHTML) { div.append(this.customHTML()); From 6bd4a159e641b24a4873601ed066db2162a7ec33 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 5 Aug 2026 19:58:39 +0000 Subject: [PATCH 56/66] feat: more compact friend list Signed-off-by: lizzie --- src/webpage/style.css | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 80a918a..dec9aac 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -4891,18 +4891,15 @@ fieldset input[type="radio"] { .friendcontainer { display: flex; width: 100%; - padding: 0.2in; + padding: 0.15in; overflow-y: auto; height: 100%; align-items: stretch; box-sizing: border-box; - > div { - background: #00000030; - margin-bottom: 0.1in; padding: 0.06in 0.1in; - border-radius: 0.1in; - border: solid 1px var(--black); + margin: 0 0; + border-bottom: solid 1px var(--secondary-bg); } } .fixedsearch { @@ -4961,16 +4958,15 @@ fieldset input[type="radio"] { } .friendlyButton { padding: 0.07in; - background: #00000045; transition: background 0.2s; border-radius: 1in; - border: solid 1px var(--black); + border: solid 1px var(--secondary-bg); width: 24px; height: 24px; margin: 0 0.05in; } .friendlyButton:hover { - background: black; + background: var(--secondary-hover); } .stickerOption { border: solid 1px var(--black); From a0b317a749993cfd85e484af179cfb32fa5b496c Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 5 Aug 2026 15:47:05 -0500 Subject: [PATCH 57/66] fix sticker in messages --- src/webpage/message.ts | 2 +- src/webpage/style.css | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index b17d0de..1839bb4 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1294,7 +1294,7 @@ class Message extends SnowFlake { for (const sticker of this.stickers) { stickerArea.append(sticker.getHTML()); } - div.append(stickerArea); + text.append(stickerArea); if (this.poll) { const pollbody = document.createElement("div"); diff --git a/src/webpage/style.css b/src/webpage/style.css index 80a918a..371c5fb 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -5181,7 +5181,6 @@ img.error::after { border-radius: 2in; } .stickerMArea { - padding-left: 48px; } .solidBackground { background: var(--secondary-bg); From 9732009aa5fb6567bf781b8fbadc423168e57fc9 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 5 Aug 2026 20:49:06 +0000 Subject: [PATCH 58/66] fix: align channel names vertically Signed-off-by: lizzie --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 80a918a..20f3335 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2160,6 +2160,9 @@ span.instanceStatus { display: flex; align-items: center; transition: font-weight 0.1s; + .ellipsis { + margin-bottom: 0.25em; + } } .channelbutton:hover { background: var(--channel-hover); From e584de6d90ec7eb0c38896553c9fb821d18e2288 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 5 Aug 2026 21:00:39 +0000 Subject: [PATCH 59/66] use child selector --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 20f3335..b3ae05f 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2160,7 +2160,7 @@ span.instanceStatus { display: flex; align-items: center; transition: font-weight 0.1s; - .ellipsis { + > .ellipsis { margin-bottom: 0.25em; } } From fac8a15f7cff9be14dbaabdf84afb2c5713f2ebb Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 6 Aug 2026 14:27:43 +0200 Subject: [PATCH 60/66] Localisation updates from https://translatewiki.net. --- translations/hu.json | 408 ++++++++++++++++++++++++++++++++++++++ translations/it.json | 1 + translations/ko.json | 21 +- translations/zh-hans.json | 3 + 4 files changed, 430 insertions(+), 3 deletions(-) create mode 100644 translations/hu.json diff --git a/translations/hu.json b/translations/hu.json new file mode 100644 index 0000000..738ad31 --- /dev/null +++ b/translations/hu.json @@ -0,0 +1,408 @@ +{ + "@metadata": { + "authors": [ + "Dj", + "Koko1998" + ] + }, + "2faCode": "Kétfaktoros hitelesítési kód:", + "404": { + "404": "Hiba: 404 – Az oldal nem található", + "app": "Az alkalmazáshoz", + "home": "Kezdőlap", + "login": "Bejelentkezés", + "reset": "Jelszó-visszaállítási oldal", + "title": "Úgy tűnik, elvesztél", + "whereever": "Bárhol is legyen ez" + }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, + "onboarding": { + "addChannel": "Csatorna hozzáadása", + "channel": "Csatorna:", + "desc": "Leírás:" + }, + "DMs": { + "markRead": "Megjelölés olvasottként", + "name": "Közvetlen üzenetek" + }, + "ok": "Rendben", + "dismiss": "Elvetés", + "accessibility": { + "gifSettings": { + "always": "Mindig", + "never": "Soha" + }, + "name": "Akadálymentesség", + "visuals": "Vizuális elemek", + "animations": "Animációk", + "playGif": "GIF-ek lejátszása:", + "playIcon": "Animált ikonok lejátszása:" + }, + "badge": { + "premium": "Prémium", + "verified_developer": "Ellenőrzött fejlesztő" + }, + "blog": { + "blog": "Blog", + "gotoPost": "Ugrás a bejegyzéshez" + }, + "cancel": "Mégse", + "channel": { + "announcement": "Bejelentések", + "createCatagory": "Kategória létrehozása", + "createChannel": "Csatorna létrehozása", + "creating": "Csatorna létrehozása", + "delete": "Csatorna törlése", + "deleteCat": "Kategória törlése", + "icon": "Ikon:", + "makeInvite": "Meghívó küldése", + "markRead": "Megjelölés olvasottként", + "mute": "Csatorna némítása", + "name": "Csatorna", + "name:": "Csatorna neve:", + "permissions": "Engedélyek", + "selectCatName": "Kategória neve", + "selectName": "Csatorna neve", + "selectType": "Csatornatípus kiválasztása", + "settings": "Beállítások", + "slowmode": "Lassú üzemmód:", + "text": "Szöveg", + "topic:": "Téma:", + "forum": "Fórum" + }, + "forum": { + "next": "Következő", + "back": "Vissza", + "newPost": "Új bejegyzés létrehozása", + "post": "Bejegyzés", + "sortOptions": { + "sortby": { + "title": "Rendezés", + "recent": "Nemrég aktív", + "posted": "Közzététel dátuma" + }, + "sortOrder": { + "title": "Rendezési sorrend" + } + } + }, + "copyRegLink": "Hivatkozás másolása", + "createAccount": "Fiók létrehozása", + "delete": "Törlés", + "deleteConfirm": "Biztosan törölni szeretnéd ezt?", + "devSettings": { + "name": "Fejlesztői beállítások" + }, + "deviceManage": { + "city": "Város: $1", + "continent": "Kontinens: $1", + "country": "Ország: $1", + "ip": "Utolsó ismert IP-cím: $1", + "logout": "Kijelentkezés", + "manageDev": "Eszköz kezelése", + "region": "Régió: $1" + }, + "dms": "Közvetlen üzenetek", + "edit": "Szerkesztés", + "emoji": { + "confirmDel": "Biztos, hogy törölni akarod ezt az emojit?", + "image:": "Kép:", + "name:": "Név:", + "title": "Emojik", + "upload": "Emojik feltöltése" + }, + "emojiSelect": "Emoji:", + "favoriteGifs": "Kedvenc GIF-ek", + "folder": { + "color": "Mappa színe:", + "create": "Új mappa létrehozása", + "edit": "Mappa szerkesztése", + "name": "Mappa neve:" + }, + "form": { + "captcha": "Várjunk csak, ember vagy?" + }, + "friends": { + "addfriend": "Barát hozzáadása", + "addfriendpromt": "Barátok hozzáadása felhasználónév alapján:", + "requestsent": "Kérés elküldve!", + "all": "Mind", + "all:": "Minden barát:", + "blocked": "Blokkolva", + "blockedusers": "Blokkolt felhasználók", + "bu": "Blokkolt felhasználók", + "friendlist": "Barátlista", + "friends": "Barátok", + "notfound": "A felhasználó nem található", + "online": "Elérhető", + "online:": "Elérhető barátok:", + "pending": "Függőben", + "pending:": "Függőben lévő barátkérelmek:", + "removeFriend": "Barát eltávolítása" + }, + "group": { + "edit": "Csoportos csevegés szerkesztése", + "select": "Barátok kijelölése" + }, + "guild": { + "INVITES_DISABLED": "Csak meghívottak számára", + "adminMenu": { + "ownName": "Tulajdonos", + "permission": "Engedélyek:" + }, + "banReason": "Kitiltás oka: $1", + "bannedBy": "Kitiltotta:", + "bans": "Kitiltások", + "idSel": "Azonosító:", + "community": "Közösség", + "createNewTemplate": "Sablon létrehozása", + "description:": "Leírás:", + "icon:": "Ikon:", + "invites": "Meghívók", + "loadingDiscovery": "Betöltés…", + "makeInvite": "Meghívó küldése", + "markRead": "Megjelölés olvasottként", + "name:": "Név:", + "nameNoMatch": "A nevek nem egyeznek", + "noDelete": "Mindegy", + "noLeave": "Mindegy", + "none": "Egyik sem", + "notifications": "Értesítések", + "overview": "Áttekintés", + "region:": "Régió:", + "roles": "Szerepkörök", + "settings": "Beállítások", + "tempCreatedBy": "A sablont készítette:", + "templateDesc": "A sablon leírása:", + "templateName": "A sablon neve:", + "templateURL": "Sablon linkje: $1", + "templates": "Sablonok", + "topic:": "Téma:", + "viewTemplate": "Sablon megtekintése", + "yesDelete": "Igen, biztos vagyok benne", + "yesLeave": "Igen, biztos vagyok benne" + }, + "donate": { + "title": "Adományozási lehetőségek", + "donate": "Adományozás" + }, + "htmlPages": { + "createAccount": "Fiók létrehozása", + "dobField": "Születési dátum", + "emailField": "E-mail:", + "instanceField": "Példa:", + "loaddesc": "Ez nem fog sokáig tartani", + "loginButton": "Bejelentkezés", + "noAccount": "Nincs még fiókod?", + "pw2Field": "Írd be a jelszót újra:", + "pwField": "Jelszó:", + "switchaccounts": "Fiókváltás", + "trans": "Fordítás", + "userField": "Felhasználónév:" + }, + "instanceStats": { + "members": "Tagok: $1", + "messages": "Üzenetek: $1", + "users": "Regisztrált felhasználók: $1" + }, + "invite": { + "channel:": "Csatorna:", + "createInvite": "Meghívó létrehozása", + "createdAt": "Létrehozva ekkor $1", + "expireAfter": "Lejár ezután:", + "expires": "Lejár ekkor: $1", + "never": "Soha" + }, + "inviteOptions": { + "12h": "12 óra", + "1d": "1 nap", + "1h": "1 óra", + "30d": "30 nap", + "30m": "30 perc", + "6h": "6 óra", + "7d": "7 nap", + "never": "Soha", + "noLimit": "Nincs korlát", + "title": "Hívj meg embereket" + }, + "localuser": { + "2faCode:": "Kód:", + "2fa": "Kétfaktoros hitelesítés beállításai", + "2faDisable": "A kétfaktoros hitelesítés letiltása", + "2faEnable": "A kétfaktoros hitelesítés engedélyezése", + "CheckUpdate": "Frissítések ellenőrzése", + "PasswordsNoMatch": "A jelszavak nem egyeznek", + "accountSettings": "Fiók beállításai", + "badCode": "Érvénytelen kód", + "badPassword": "Érvénytelen jelszó", + "changeEmail": "E-mail cím módosítása", + "changePassword": "Jelszó módosítása", + "changeUsername": "Felhasználónév módosítása", + "clearCache": "Gyorsítótár törlése", + "newEmail:": "Új e-mail", + "newPassword:": "Új jelszó:", + "newUsername": "Új felhasználónév:", + "noUpdates": "Nem található frissítés", + "notisound": "Értesítési hang:", + "oldPassword:": "Régi jelszó:", + "password:": "Jelszó", + "setUp2fa": "A kétfaktoros hitelesítés beállítása", + "settings": "Beállítások", + "status": "Állapot", + "team:": "Csapat:", + "theme:": "Téma", + "themesAndSounds": "Témák és hangok", + "updateSettings": "Beállítások frissítése", + "updatesYay": "Frissítések találhatók!", + "userSettings": "A nyilvános profilod", + "general": "Általános" + }, + "login": { + "enterPAgain": "Írd be a jelszót újra:", + "login": "Bejelentkezés", + "newPassword": "Új jelszó:", + "recover": "Elfelejtett jelszó?", + "recovery": "Elfelejtett jelszó" + }, + "logout": { + "error": { + "cancel": "Mégse", + "cont": "Folytatás mindenképpen" + }, + "logout": "Kijelentkezés" + }, + "manageInstance": { + "format": "Formátum:", + "length": "Hossz:" + }, + "media": { + "artist": "Művész: $1", + "composer": "Zeneszerző: $1", + "download": "Média letöltése", + "length": "Hossz: $1 perc és $2 másodperc", + "loading": "Betöltés", + "moreInfo": "További információ", + "notFound": "Média nem található" + }, + "member": { + "nick:": "Becenév:", + "reason:": "Indoklás:" + }, + "message": { + "delete": "Üzenet törlése", + "report": "Üzenet jelentése", + "deleted": "Törölt üzenet", + "edit": "Üzenet szerkesztése", + "edited": "(szerkesztve)", + "fullMessage": "Teljes üzenet:", + "reactionAdd": "Reakció hozzáadása", + "reactionsTitle": "Reakciók" + }, + "report": { + "back": "Vissza", + "next": "Következő", + "cancel": "Mégse" + }, + "nevermind": "Mindegy", + "notiVolume": "Értesítés hangereje:", + "pinMessage": "Üzenet rögzítése", + "profile": { + "bio": "Rólam:", + "mutFriends": "Közös barátok", + "permInfo": "Engedélyek", + "userInfo": "Felhasználói információk", + "notes": "Felhasználói megjegyzések" + }, + "profileColor": "Profil színe", + "recentEmoji": "Legutóbbi emojik", + "register": { + "DOBError": "Születési dátum: $1", + "agreeTOS": "Elfogadom a [Szolgáltatási feltételeket]($1):", + "emailError": "E-mail: $1", + "passwordError:": "Jelszó: $1", + "usernameError": "Felhasználónév: $1", + "instURL": "URL:" + }, + "remove": "Eltávolítás", + "role": { + "displaySettings": "Megjelenítési beállítások", + "name": "Szerepkör neve:", + "perms": "Engedélyek", + "remove": "Szerepkör eltávolítása", + "roleEmoji": "Szerepkör emoji:", + "roleFileIcon": "Szerepkör ikon:", + "roles": "Szerepkörök" + }, + "upload": "Fájlok feltöltése", + "makePoll": "Szavazás létrehozása", + "poll": { + "question": "Kérdés:", + "answers": "Válaszok:", + "newAnswer": "Új válasz", + "duration": "Időtartam:", + "durCount": { + "1": "1 óra", + "4": "4 óra", + "8": "8 óra", + "24": "24 óra", + "72": "3 nap", + "168": "7 nap", + "336": "14 nap" + }, + "expires": "Lejár ekkor: $1" + }, + "search": { + "back": "Vissza", + "new": "Új", + "next": "Következő", + "old": "Régi", + "search": "Keresés", + "settings": "Keresési beállítások", + "authors": "Szerzők:", + "mentions": "Említések:", + "channels": "Csatornák:" + }, + "settings": { + "img": "Kép feltöltése", + "save": "Módosítások mentése", + "unsaved": "Vigyázz, vannak nem mentett módosításaid" + }, + "sticker": { + "desc": "Leírás", + "image": "Kép:", + "name": "Név:" + }, + "switchAccounts": "Fiókváltás ⇌", + "connections": { + "since": "Tagság kezdete: $1", + "delete": "Kapcsolat törlése", + "sure": "Biztos vagy benne?" + }, + "unpinMessage": "Üzenet rögzítésének megszüntetése", + "updateAv": "Frissítések elérhetők", + "useTemplateButton": "Sablon használata", + "user": { + "dnd": "Ne zavarj", + "editNick": "Becenév szerkesztése", + "friendReq": "Barátkérelem", + "idle": "Tétlen", + "invisible": "Láthatatlan", + "offline": "Offline", + "online": "Online", + "remove": "Felhasználó eltávolítása", + "unblock": "Felhasználó blokkolásának feloldása", + "viewProfile": "Profil megtekintése" + }, + "keyboard": { + "empty": "", + "descs": { + "gifSearch": "GIF-ek keresése:" + } + }, + "yes": "Igen" +} diff --git a/translations/it.json b/translations/it.json index ad5326f..4e75b20 100644 --- a/translations/it.json +++ b/translations/it.json @@ -115,6 +115,7 @@ }, "bio": "Biografia:", "blankMessage": "Messaggio vuoto", + "sentPoll": "Inviato un sondaggio", "blog": { "blog": "Blog", "blogUpdates": "Ricevi aggiornamenti dal blog quando apri l'app:", diff --git a/translations/ko.json b/translations/ko.json index a0772c5..b13ed59 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -44,6 +44,7 @@ "DMs": { "add": "이 메시지에 누군가를 추가", "close": "DM 닫기", + "copyURL": "DM URL 복사", "copyId": "DM ID 복사", "markRead": "읽은 것으로 표시", "name": "직신" @@ -121,6 +122,9 @@ }, "bio": "자기 소개:", "blankMessage": "빈 메시지", + "sentAttachment": "첨부 파일을 보냈습니다", + "sentPoll": "설문 조사를 보냈습니다", + "sentSticker": "스티커를 보냈습니다", "blog": { "blog": "블로그", "blogUpdates": "앱을 열었을 때 블로그 업데이트 받기:", @@ -135,6 +139,7 @@ "cancel": "취소", "channel": { "SlowmodeCool": "저속 모드 쿨다운: $1", + "copyURL": "채널 URL 복사", "TimeOutCool": "다음 시간까지 타임아웃: $1", "allowIcons": "사용자 지정 채널 아이콘 허용", "announcement": "알림", @@ -751,7 +756,8 @@ "REQUEST_TO_SPEAK": "역할 멤버가 스테이지 채널에서 말할 수 있도록 허용합니다.", "USE_EMBEDDED_ACTIVITIES": "역할 멤버가 임베디드 활동을 사용할 수 있도록 허용합니다.", "USE_APPLICATION_COMMANDS": "역할 멤버가 애플리케이션 명령을 사용할 수 있도록 허용합니다.", - "USE_EXTERNAL_APPS": "역할 멤버가 애플리케이션 응답을 채널에 공개적으로 표시할 수 있도록 허용합니다. (비활성화하더라도 사용자는 앱을 계속 사용할 수 있지만 응답은 본인에게만 표시됩니다. 이 기능은 길드에 설치되지 않은 앱에만 적용됩니다.)" + "USE_EXTERNAL_APPS": "역할 멤버가 애플리케이션 응답을 채널에 공개적으로 표시할 수 있도록 허용합니다. (비활성화하더라도 사용자는 앱을 계속 사용할 수 있지만 응답은 본인에게만 표시됩니다. 이 기능은 길드에 설치되지 않은 앱에만 적용됩니다.)", + "SET_VOICE_CHANNEL_STATUS": "음성 채널 상태 설정" }, "readableNames": { "ADD_REACTIONS": "반응 추가", @@ -804,7 +810,8 @@ "VIEW_AUDIT_LOG": "감사 로그 보기", "VIEW_CHANNEL": "채널 보기", "VIEW_CREATOR_MONETIZATION_ANALYTICS": "크리에이터 수익 창출 분석 보기", - "VIEW_GUILD_INSIGHTS": "길드 분석 정보 보기" + "VIEW_GUILD_INSIGHTS": "길드 분석 정보 보기", + "SET_VOICE_CHANNEL_STATUS": "음성 채널 상태 설정" } }, "pinMessage": "메시지 고정", @@ -818,6 +825,7 @@ "userInfo": "사용자 정보", "notes": "사용자 메모" }, + "messageNotLoad": "메시지를 불러오는 데 실패했습니다", "profileColor": "프로필 색상", "pronouns": "대명사:", "readableName": "한국어", @@ -1002,7 +1010,14 @@ "unknown": "@unknown-user" }, "keyboard": { - "shortcuts": "단축키" + "shortcuts": "단축키", + "descs": { + "gifSearch": "GIF 검색:" + } + }, + "domain": { + "title": "도메인 검증", + "domain": "도메인:" }, "vc": { "joinForStream": "음성 채널에 참여해 시청", diff --git a/translations/zh-hans.json b/translations/zh-hans.json index da9421d..4aae249 100644 --- a/translations/zh-hans.json +++ b/translations/zh-hans.json @@ -127,6 +127,9 @@ }, "bio": "个人简介:", "blankMessage": "空白消息", + "sentAttachment": "发送了一个附件", + "sentPoll": "发送了一个投票", + "sentSticker": "发送了一张贴纸", "blog": { "blog": "博客", "blogUpdates": "打开应用时获取博客更新:", From f05ca639c335f7cfc207161942149cac953f0f96 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 6 Aug 2026 13:40:02 -0500 Subject: [PATCH 61/66] that was meant to be static --- src/webpage/hover.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts index 017ff37..01630c5 100644 --- a/src/webpage/hover.ts +++ b/src/webpage/hover.ts @@ -68,13 +68,13 @@ class Hover { this.elm2.remove(); }); } - prevDiv = new WeakRef(document.createElement("div")); + static prevDiv = new WeakRef(document.createElement("div")); async makeHover(elm: HTMLElement) { - const prev = this.prevDiv.deref(); + const prev = Hover.prevDiv.deref(); if (prev) prev.remove(); if (!document.contains(elm)) return document.createElement("div"); const div = document.createElement("div"); - this.prevDiv = new WeakRef(div); + Hover.prevDiv = new WeakRef(div); if (this.customHTML) { div.append(this.customHTML()); From c386328c7fcb8456d72c3cee85e67529a9976938 Mon Sep 17 00:00:00 2001 From: softgaypaws Date: Thu, 6 Aug 2026 23:30:40 +0200 Subject: [PATCH 62/66] update padding to match the chatbox's height --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 371c5fb..4f54d77 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2376,7 +2376,7 @@ span.instanceStatus { } #userdock { - padding: 4px 6px; + padding: 11.8px 6px; background: var(--dock-bg); align-items: center; justify-content: space-between; From ce71de848becbcc1209bfdfabc8b9b092a300503 Mon Sep 17 00:00:00 2001 From: softgaypaws Date: Thu, 6 Aug 2026 23:32:47 +0200 Subject: [PATCH 63/66] Codebergs File Editor Is making Me angry --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 4f54d77..fcc497a 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2376,7 +2376,7 @@ span.instanceStatus { } #userdock { - padding: 11.8px 6px; + padding: 11.75px 6px; background: var(--dock-bg); align-items: center; justify-content: space-between; From bed19cfd57f5a7e0248a479148c9593ff1fd904d Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 9 Aug 2026 17:10:27 -0500 Subject: [PATCH 64/66] fix minor embed fix --- src/webpage/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 371c5fb..60b0b3e 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3268,6 +3268,8 @@ img.bigembedimg { margin-top: 8px; border-radius: 4px; cursor: pointer; + width: -webkit-fill-available; + width: -moz-available; } .inviteEmbed { width: 400px; From c3740b27724ed2f277cbb601adf80988e0cdecff Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 9 Aug 2026 17:15:20 -0500 Subject: [PATCH 65/66] fix invite bug --- src/webpage/invite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index 9c29bce..4bb3c53 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -12,6 +12,7 @@ if (window.location.pathname.startsWith("/invite")) } console.log(m); well = m.get(well.toLowerCase()) || (await getInstanceInfo(well))?.api || well; + well = well.replace(/\/*$/gm, ""); const joinable: Specialuser[] = []; for (const key in users.users) { @@ -20,7 +21,6 @@ if (window.location.pathname.startsWith("/invite")) if (well && user.serverurls.wellknown.includes(well)) { joinable.push(user); } - console.log(user); } } From 412a1950a76ec84ee5d58048b9ceea03f80bfd9f Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 9 Aug 2026 18:57:13 -0500 Subject: [PATCH 66/66] seperate emoji/sticker settings for animation --- src/webpage/emoji.ts | 21 +++++++++-------- src/webpage/localuser.ts | 18 +++++++++++++++ src/webpage/sticker.ts | 3 +++ src/webpage/utils/storage/userPreferences.ts | 2 ++ src/webpage/utils/utils.ts | 24 ++++++++++++++------ translations/en.json | 2 ++ 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 13cde44..2d195d8 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -6,7 +6,7 @@ import {emojijson, emojiSource} from "./jsontypes.js"; import {Localuser} from "./localuser.js"; import {BinRead} from "./utils/binaryUtils.js"; import {CDNParams} from "./utils/cdnParams.js"; -import {removeAni} from "./utils/utils.js"; +import {createImg, removeAni} from "./utils/utils.js"; //I need to recompile the emoji format for translation class Emoji { @@ -89,17 +89,20 @@ class Emoji { getHTML(bigemoji: boolean = false, click = true) { if (this.id) { if (!this.owner) throw new Error("owner is missing for custom emoji!"); - const emojiElem = document.createElement("img"); + const emojiElem = createImg( + this.owner.info.cdn + + "/emojis/" + + this.id + + "." + + (this.animated ? "gif" : "png") + + new CDNParams({expectedSize: 32, animated: this.animated}), + undefined, + undefined, + "emoji", + ); emojiElem.classList.add("md-emoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.crossOrigin = "anonymous"; - emojiElem.src = - this.owner.info.cdn + - "/emojis/" + - this.id + - "." + - (this.animated ? "gif" : "png") + - new CDNParams({expectedSize: 32, animated: this.animated}); emojiElem.alt = this.name; emojiElem.loading = "lazy"; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 4e4ea5f..67423b4 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -3305,6 +3305,24 @@ class Localuser { AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()), {defaultIndex: AnimateTristateValues.indexOf(prefs.animateIcons)}, ); + animations.addSelect( + I18n.accessibility.playEmoji(), + async (i) => { + prefs.animateEmoji = AnimateTristateValues[i]; + await setPreferences(prefs); + }, + AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()), + {defaultIndex: AnimateTristateValues.indexOf(prefs.animateEmoji)}, + ); + animations.addSelect( + I18n.accessibility.playSticker(), + async (i) => { + prefs.animateSticker = AnimateTristateValues[i]; + await setPreferences(prefs); + }, + AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()), + {defaultIndex: AnimateTristateValues.indexOf(prefs.animateSticker)}, + ); } settings.addButton(I18n.localuser.security(), {head: true}); { diff --git a/src/webpage/sticker.ts b/src/webpage/sticker.ts index e361ba6..4c8f287 100644 --- a/src/webpage/sticker.ts +++ b/src/webpage/sticker.ts @@ -36,6 +36,9 @@ class Sticker extends SnowFlake { getHTML(): HTMLElement { const img = createImg( this.owner.info.cdn + "/stickers/" + this.id + ".webp" + new CDNParams({expectedSize: 160}), + undefined, + undefined, + "sticker", ); img.classList.add("sticker"); const hover = new Hover(this.name); diff --git a/src/webpage/utils/storage/userPreferences.ts b/src/webpage/utils/storage/userPreferences.ts index 6373e56..e52d322 100644 --- a/src/webpage/utils/storage/userPreferences.ts +++ b/src/webpage/utils/storage/userPreferences.ts @@ -30,6 +30,8 @@ export class UserPreferences { // render settings animateIcons: AnimateTristateValue = AnimateTristateValue.OnlyOnHover; animateGifs: AnimateTristateValue = AnimateTristateValue.OnlyOnHover; + animateEmoji: AnimateTristateValue = AnimateTristateValue.Always; + animateSticker: AnimateTristateValue = AnimateTristateValue.Always; renderJoinAvatars: boolean = true; theme: ThemeOption = ThemeOption.Dark; accentColor: string = "#5865F2"; diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index c714636..3283089 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -662,18 +662,28 @@ export type safeImg = HTMLImageElement & { setSrcs: (nsrc: string, nstaticsrc: string | void) => void; isAnimated: () => Promise; }; +function getSetting(type: "gif" | "icon" | "emoji" | "sticker") { + const prefs = getPreferences(); + switch (type) { + case "gif": + return prefs.animateGifs; + case "icon": + return prefs.animateIcons; + case "emoji": + return prefs.animateEmoji; + case "sticker": + return prefs.animateSticker; + default: + return "hover"; + } +} export function createImg( src: string | undefined, staticsrc: string | void, elm: HTMLElement | void, - type: "gif" | "icon" = "gif", + type: "gif" | "icon" | "emoji" | "sticker" = "gif", ): safeImg { - const prefs = getPreferences(); - const aniOpt = - (type === "gif" ? prefs.animateGifs : prefs.animateIcons) || - ("hover" as "hover") || - "always" || - "never"; + const aniOpt = getSetting(type); const img = document.createElement("img"); img.loading = "lazy"; img.decoding = "async"; diff --git a/translations/en.json b/translations/en.json index 4c2cadf..9532dc1 100644 --- a/translations/en.json +++ b/translations/en.json @@ -76,6 +76,8 @@ "animations":"Animations", "playGif": "Play GIFs:", "playIcon": "Play animated icons:", + "playEmoji":"Play animated emoji:", + "playSticker":"Play animated stickers:", "roleColors": "Disable role colors:", "gradientColors":"Disable gradient coloring:", "decorations":"Enable avatar decorations:"