From 1e589e5d35ac235a69599eab6dda666aa49ee320 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 29 Jul 2026 04:17:18 +0000 Subject: [PATCH 01/45] feat: make 'Today at xx:xx AM/PM' just be 'xx:xx AM/PM' for brevity Signed-off-by: lizzie --- 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 ba4c753..1cf05fa 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1779,7 +1779,7 @@ function formatTime(date: Date) { date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"}); if (datestring === now) { - return I18n.todayAt(formatTime(date)); + return formatTime(date); } else if (datestring === yesterdayStr) { return I18n.yesterdayAt(formatTime(date)); } else { From 341a8e1d7f6d93dba04e5fb10f66011b4dd97157 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 11:55:15 -0500 Subject: [PATCH 02/45] unknown file changes --- src/webpage/file.ts | 13 ++++++------- src/webpage/style.css | 8 ++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/webpage/file.ts b/src/webpage/file.ts index fecca48..bd44b50 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -276,16 +276,15 @@ class File { createunknown(url: Promise | void): HTMLElement { console.log("🗎"); const src = this.proxy_url || this.url; - const div = document.createElement("table"); - div.classList.add("unknownfile"); - const nametr = document.createElement("tr"); - div.append(nametr); + const div = document.createElement("div"); + div.classList.add("unknownfile", "flexltr"); const fileicon = document.createElement("td"); - nametr.append(fileicon); + div.append(fileicon); fileicon.append("🗎"); fileicon.classList.add("fileicon"); fileicon.rowSpan = 2; - const nametd = document.createElement("td"); + const nametd = document.createElement("div"); + nametd.classList.add("flexttb"); if (src) { const a = document.createElement("a"); a.href = src; @@ -300,7 +299,7 @@ class File { } nametd.classList.add("filename"); - nametr.append(nametd); + div.append(nametd); const sizetr = document.createElement("tr"); const size = document.createElement("td"); sizetr.append(size); diff --git a/src/webpage/style.css b/src/webpage/style.css index c446e6c..506c62c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2488,6 +2488,14 @@ span.instanceStatus { border-radius: 8px; overflow: hidden; height: 192px; + .unknownfile { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } } .messageimgdiv { position: relative; From 221fa97f18026ec576b20aea4b4d831cf0bd7e16 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 12:54:47 -0500 Subject: [PATCH 03/45] make updating permissions work less bad --- src/webpage/channel.ts | 31 +++---------------------------- src/webpage/guild.ts | 13 +++++++++---- src/webpage/role.ts | 30 ++++++------------------------ 3 files changed, 18 insertions(+), 56 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index e405ac8..ef9211e 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -3236,42 +3236,17 @@ class Channel extends SnowFlake { this.parentId = undefined; } - const oldover = this.permissionOverwriteMap; - this.permissionOverwriteMap.clear(); (json.permission_overwrites ?? []).forEach((r) => { const p = new Permissions(r.allow, r.deny); this.permissionOverwriteMap.set(r.id, p); }); - const nchange = new Set(oldover.keys()).difference(this.permissionOverwriteMap); - const pchange = new Set(this.permissionOverwriteMap.keys()).difference(oldover); - for (const thing of nchange) { - const role = this.guild.roleids.get(thing); - if (role) { - this.croleUpdate(role, new Permissions("0"), false); - } else { - const user = this.localuser.getUser(thing); - user.then((_) => { - if (_) this.croleUpdate(_, new Permissions("0"), false); - }); - } - } - for (const thing of pchange) { - const role = this.guild.roleids.get(thing); - const perms = this.permissionOverwriteMap.get(thing); - if (role && perms) { - this.croleUpdate(role, perms, true); - } else if (perms) { - const user = this.localuser.getUser(thing); - user.then((_) => { - if (_) this.croleUpdate(_, perms, true); - }); - } - } + + this.croleUpdate(); this.topic = json.topic; this.nsfw = json.nsfw; this.fireEvents(); } - croleUpdate: (role: Role | User, perm: Permissions, added: boolean) => unknown = () => {}; + croleUpdate: () => unknown = () => {}; typingstart() { if (this.typing > Date.now()) { return; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index c6704e1..6861bce 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -554,6 +554,14 @@ class Guild extends SnowFlake { dio.show(); } + regenRLPerms() { + this.sortRoles(); + const permlist: [Role, Permissions][] = []; + for (const thing of this.roles) { + permlist.push([thing, thing.permissions]); + } + return permlist; + } generateSettings() { const settings = new Settings(I18n.guild.settingsFor(this.properties.name)); const textChannels = this.channels.filter((e) => { @@ -697,10 +705,7 @@ class Guild extends SnowFlake { this.makeInviteMenu(settings.addButton(I18n.invite.inviteMaker()), textChannels); if (this.member.hasPermission("MANAGE_ROLES")) { const s1 = settings.addButton(I18n.guild.roles(), {optName: ""}); - const permlist: [Role, Permissions][] = []; - for (const thing of this.roles) { - permlist.push([thing, thing.permissions]); - } + const permlist = this.regenRLPerms(); s1.options.push(new RoleList(permlist, this, this.updateRolePermissions.bind(this), false)); } if (this.member.hasPermission("MANAGE_GUILD_EXPRESSIONS")) { diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 604184d..77d4a53 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -283,28 +283,15 @@ class RoleList extends Buttons { }); } this.options = options; - guild.roleUpdate = this.groleUpdate.bind(this); + guild.roleUpdate = this.roleUpdate.bind(this); if (channel) { - channel.croleUpdate = this.croleUpdate.bind(this); + channel.croleUpdate = this.roleUpdate.bind(this); } } - private groleUpdate(role: Role, added: 1 | 0 | -1) { - if (!this.channel) { - if (added === 1) { - this.permissions.push([role, role.permissions]); - } - } - if (added === -1) { - this.permissions = this.permissions.filter((r) => r[0] !== role); - } - this.redoButtons(); - } - private croleUpdate(role: Role | User, perm: Permissions, added: boolean) { - if (added) { - this.permissions.push([role, perm]); - } else { - this.permissions = this.permissions.filter((r) => r[0] !== role); - } + private async roleUpdate() { + this.permissions = this.channel + ? await this.channel.getOverwritesOrder() + : this.guild.regenRLPerms(); this.redoButtons(); } makeguildmenus(option: Options) { @@ -526,11 +513,6 @@ class RoleList extends Buttons { } redoButtons() { this.buttons = []; - this.permissions.sort(([a], [b]) => { - if (b instanceof User || !(b instanceof Channel)) return 1; - if (a instanceof User || !(a instanceof Channel)) return -1; - return b.position - a.position; - }); for (const i of this.permissions) { this.buttons.push({ name: "name" in i[0] ? i[0].name : I18n.userping.unknown(), From 82180be417a8b27ac0387cb2b6f7044122444e23 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 13:02:17 -0500 Subject: [PATCH 04/45] make guild update on update --- src/webpage/guild.ts | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 6861bce..962b479 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -1479,17 +1479,20 @@ class Guild extends SnowFlake { this.emojis = json.emojis || []; this.headers = this.owner.headers; this.welcomeScreen = json.welcome_screen; - this.properties.features = json.features; - if (this.properties.icon !== json.icon) { - this.properties.icon = json.icon; - if (this.HTMLicon) { - const divy = this.generateGuildIcon(); - this.HTMLicon.replaceWith(divy); - this.HTMLicon = divy; + if (this.properties) + if (this.properties.icon !== json.icon) { + this.properties.icon = json.icon; + if (this.HTMLicon) { + const divy = this.generateGuildIcon(); + this.HTMLicon.replaceWith(divy); + this.HTMLicon = divy; + } } - } this.roleids.clear(); this.banner = json.banner; + this.welcomeScreen = json.welcome_screen; + + this.properties = json; } constructor(json: guildjson | -1, owner: Localuser, member: memberjson | User | null) { super(typeof json === "number" ? "@me" : json.id); @@ -1500,20 +1503,14 @@ class Guild extends SnowFlake { if (json === -1 || member === null) { return; } - if (json.stickers.length) { - console.log(json.stickers, ":3"); - } - this.large = json.large; + this.update({...json.properties, large: json.large, emojis: json.emojis}); + this.member_count = json.member_count; - this.emojis = json.emojis || []; this.channels = []; - if (json.properties) { - this.properties = json.properties; - } + this.roles = []; - this.banner = json.properties.banner; - this.welcomeScreen = json.properties.welcome_screen; + if (json.roles) { for (const roley of json.roles) { const roleh = new Role(roley, this); From a5cfdb0de0bbddc3b5c854fff4e5d80a42f8dae9 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 13:12:02 -0500 Subject: [PATCH 05/45] fix perms --- src/webpage/permissions.ts | 3 +++ translations/en.json | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/webpage/permissions.ts b/src/webpage/permissions.ts index f2e8bf6..922f86d 100644 --- a/src/webpage/permissions.ts +++ b/src/webpage/permissions.ts @@ -36,6 +36,7 @@ class Permissions { //private static info: { name: string; readableName: string; description: string }[]; static *info(): Generator<{name: string; readableName: string; description: string}> { for (const thing of this.permisions) { + if (!thing) continue; yield { name: thing, readableName: I18n.permissions.readableNames[thing](), @@ -91,6 +92,8 @@ class Permissions { "CREATE_EVENTS", "USE_EXTERNAL_SOUNDS", "SEND_VOICE_MESSAGES", + null, //TODO unused + "SET_VOICE_CHANNEL_STATUS", "SEND_POLLS", "USE_EXTERNAL_APPS", "PIN_MESSAGES", diff --git a/translations/en.json b/translations/en.json index dc08e76..da870ea 100644 --- a/translations/en.json +++ b/translations/en.json @@ -740,7 +740,8 @@ "REQUEST_TO_SPEAK": "Allows role members to request to speak in stage channels.", "USE_EMBEDDED_ACTIVITIES": "Allows role members to use embedded activities.", "USE_APPLICATION_COMMANDS": "Allows role members to use application commands.", - "USE_EXTERNAL_APPS": "Allows role members to have application responses to show publicly in channel (When disabled, users will still be allowed to use their apps but responses will be visible only to themselves. This only applies to apps not also installed to the guild)." + "USE_EXTERNAL_APPS": "Allows role members to have application responses to show publicly in channel (When disabled, users will still be allowed to use their apps but responses will be visible only to themselves. This only applies to apps not also installed to the guild).", + "SET_VOICE_CHANNEL_STATUS":"Set voice channel status" }, "readableNames": { "ADD_REACTIONS": "Add reactions", @@ -793,7 +794,8 @@ "VIEW_AUDIT_LOG": "View audit log", "VIEW_CHANNEL": "View channels", "VIEW_CREATOR_MONETIZATION_ANALYTICS": "View creator monetization analytics", - "VIEW_GUILD_INSIGHTS": "View guild insights" + "VIEW_GUILD_INSIGHTS": "View guild insights", + "SET_VOICE_CHANNEL_STATUS":"Set voice channel status" } }, "pinMessage": "Pin message", From 2fb3986b2783c2253a7d3cb11794175906218bbc Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 29 Jul 2026 18:29:34 +0000 Subject: [PATCH 06/45] feat: prefix channels with # sign when relevant Signed-off-by: lizzie --- translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/en.json b/translations/en.json index dc08e76..82b3c32 100644 --- a/translations/en.json +++ b/translations/en.json @@ -151,7 +151,7 @@ "text": "Text", "timedOutUntil": "Timed out until: $1", "topic:": "Topic:", - "typebox": "Message in $1", + "typebox": "Message in # $1", "unmute": "Unmute channel", "voice": "Voice", "deleteThread":"Delete thread", From f22a13f295db26bc12d1b8f0482b849a96337de1 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 14:55:38 -0500 Subject: [PATCH 07/45] merge and fixes --- src/webpage/i18n.ts | 13 ++++---- src/webpage/localuser.ts | 25 ++++++++++----- src/webpage/message.ts | 4 ++- src/webpage/utils/storage/userPreferences.ts | 3 +- src/webpage/utils/utils.ts | 32 +++++++++----------- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 160649b..1b706c6 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -119,13 +119,12 @@ class I18n { static options() { return [...langmap.keys()].map((e) => e.replace(".json", "")); } - static setLanguage(lang: string) { + static async setLanguage(lang: string) { if (this.options().indexOf(lang) !== -1) { - getPreferences().then(async (prefs) => { - prefs.locale = lang; - await I18n.create(lang); - await setPreferences(prefs); - }); + const prefs = getPreferences(); + prefs.locale = lang; + await I18n.create(lang); + await setPreferences(prefs); } } } @@ -134,7 +133,7 @@ let userLocale = navigator.language.slice(0, 2) || "en"; if (I18n.options().indexOf(userLocale) === -1) { userLocale = "en"; } -const prefs = await getPreferences(); +const prefs = getPreferences(); const storage = prefs.locale; if (storage) { userLocale = storage; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index f0b6a28..4384e75 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -337,7 +337,7 @@ class Localuser { } async queryBlog() { this.perminfo.localuser ??= {}; - const prefs = await getPreferences(); + const prefs = getPreferences(); const bstate = prefs.showBlogUpdates; if (bstate === undefined) { const pop = new Dialog(""); @@ -461,7 +461,7 @@ class Localuser { } this.pingEndpoint(); - const prefs = await getPreferences(); + const prefs = getPreferences(); const ml = document.getElementById("memberlisttoggle")!; if (prefs.checkMemberList) { ml.classList = ""; @@ -2483,7 +2483,6 @@ class Localuser { async getPosts() { const text = await (await fetch("https://blog.fermi.chat/feed_rss_created.xml")).text(); const xml = new DOMParser().parseFromString(text, "text/xml"); - console.log(xml, text); const posts = Array.from(xml.getElementsByTagName("channel")[0].getElementsByTagName("item")); return { items: posts.map((post) => { @@ -2531,7 +2530,7 @@ class Localuser { this.figureDefaultProvidor(); } async figureDefaultProvidor() { - const prefs = await getPreferences(); + const prefs = getPreferences(); this.selectedGifProfidor = this.gifProvideors.find((_) => _.api_name == prefs.gifProvidor) || this.gifProvideors[0]; } @@ -2583,7 +2582,7 @@ class Localuser { }); } async showusersettings() { - const prefs = await getPreferences(); + const prefs = getPreferences(); const localSettings = getLocalSettings(); const settings = new Settings(I18n.localuser.settings()); { @@ -3014,7 +3013,7 @@ class Localuser { } { - const prefs = await getPreferences(); + const prefs = getPreferences(); const tas = settings.addButton(I18n.localuser.themesAndSounds(), {contained: true}); { const themes = ["Dark", "WHITE", "Light", "Dark-Accent"]; @@ -3145,6 +3144,18 @@ class Localuser { {defaultIndex: ind === -1 ? 0 : ind}, ); } + { + tas.addCheckboxInput( + "Show today at:", + (b) => { + prefs.showToday = b; + setPreferences(prefs); + }, + { + initState: prefs.showToday, + }, + ); + } } { const blog = settings.addButton(I18n.blog.blog(), {contained: true}); @@ -5236,7 +5247,7 @@ class Localuser { readonly presences: Map = new Map(); static font?: FontFace; static async loadFont() { - const prefs = await getPreferences(); + const prefs = getPreferences(); const fontName = prefs.emojiFont; if (this.font) { diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 1cf05fa..ec4974e 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -27,6 +27,7 @@ import {Components} from "./interactions/compontents.js"; import {ImagesDisplay} from "./disimg"; import {ReportMenu} from "./reporting/report.js"; import {getDeveloperSettings} from "./utils/storage/devSettings.js"; +import {getPreferences} from "./utils/storage/userPreferences.js"; class Message extends SnowFlake { static contextmenu = new Contextmenu("message menu"); stickers!: Sticker[]; @@ -1773,13 +1774,14 @@ let now: string; let yesterdayStr: string; function formatTime(date: Date) { + const conf = getPreferences(); updateTimes(); const datestring = date.toLocaleDateString(); const formatTime = (date: Date) => date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"}); if (datestring === now) { - return formatTime(date); + return conf.showToday ? I18n.todayAt(formatTime(date)) : formatTime(date); } else if (datestring === yesterdayStr) { return I18n.yesterdayAt(formatTime(date)); } else { diff --git a/src/webpage/utils/storage/userPreferences.ts b/src/webpage/utils/storage/userPreferences.ts index dcf5182..6373e56 100644 --- a/src/webpage/utils/storage/userPreferences.ts +++ b/src/webpage/utils/storage/userPreferences.ts @@ -36,13 +36,14 @@ export class UserPreferences { emojiFont?: string; checkMemberList = false; gifProvidor?: string; + showToday = true; constructor(init?: Partial) { Object.assign(this, init); } } -export async function getPreferences(): Promise { +export function getPreferences(): UserPreferences { return new UserPreferences(JSON.parse(localStorage.getItem("userPreferences") || "{}")); } diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index ffb35ca..837b4e7 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -32,7 +32,7 @@ let instances: | null = null; await setTheme(); export async function setTheme(theme?: string) { - const prefs = await getPreferences(); + const prefs = getPreferences(); document.body.className = (theme || prefs.theme) + "-theme"; console.log(theme); } @@ -87,9 +87,9 @@ export function setDefaults() { userinfos.accent_color = "#3096f7"; } - getPreferences().then((perfs) => - document.documentElement.style.setProperty("--accent-color", perfs.accentColor), - ); + const perfs = getPreferences(); + document.documentElement.style.setProperty("--accent-color", perfs.accentColor); + if (userinfos.preferences === undefined) { userinfos.preferences = { theme: "Dark", @@ -749,14 +749,12 @@ export function createImg( elm: HTMLElement | void, type: "gif" | "icon" = "gif", ): safeImg { - const aniOpt = getPreferences().then((prefs) => { - return ( - (type === "gif" ? prefs.animateGifs : prefs.animateIcons) || - ("hover" as "hover") || - "always" || - "never" - ); - }); + const prefs = getPreferences(); + const aniOpt = + (type === "gif" ? prefs.animateGifs : prefs.animateIcons) || + ("hover" as "hover") || + "always" || + "never"; const img = document.createElement("img"); img.loading = "lazy"; img.decoding = "async"; @@ -769,11 +767,11 @@ export function createImg( if (animated) { img.crossOrigin = "anonymous"; } - img.src = (await aniOpt) !== "always" ? staticsrc || src || "" : src || ""; + img.src = aniOpt !== "always" ? staticsrc || src || "" : src || ""; }); } img.onload = async () => { - if ((await aniOpt) === "always") return; + if (aniOpt === "always") return; if (!src) return; if ((await isAnimated(src)) && !staticsrc) { let s = staticImgMap.get(src); @@ -797,13 +795,13 @@ export function createImg( } }; elm.addEventListener("mouseover", async () => { - if ((await aniOpt) === "never") return; + if (aniOpt === "never") return; if (img.src !== src && src) { img.src = src; } }); elm.addEventListener("mouseleave", async () => { - if (staticsrc && (await aniOpt) !== "always") { + if (staticsrc && aniOpt !== "always") { img.src = staticsrc; } }); @@ -817,7 +815,7 @@ export function createImg( if (animated) { img.crossOrigin = "anonymous"; } - img.src = (await aniOpt) !== "always" ? staticsrc || src || "" : src || ""; + img.src = aniOpt !== "always" ? staticsrc || src || "" : src || ""; }); } }, From 35c8c86b29559d3d2cefbb9ae3cb7a590e3db3a2 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 17:56:18 -0500 Subject: [PATCH 08/45] enable disabled features --- src/webpage/index.ts | 2 +- src/webpage/localuser.ts | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 0a9e61e..e6aa6ff 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -464,7 +464,7 @@ if (window.location.pathname.startsWith("/channels")) { }, { //TODO re-enable this once polls is merged - visible: () => false, //!!thisUser.channelfocus?.hasPermission("SEND_POLLS"), + visible: () => !!thisUser?.focusChannel?.hasPermission("SEND_POLLS"), }, ); umenu.addButton(I18n.upload(), () => { diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 4384e75..aea01b7 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -2777,21 +2777,20 @@ class Localuser { connectionContainer.appendChild(container); }); - //TODO enable this once domain verification is ready within Harmony - if (false as true) { - const container = document.createElement("div"); - const span = document.createElement("span"); - span.classList.add("conImg", "svgicon"); - span.style.setProperty("mask", `url("/icons/domain.svg")`); - container.append(span); + const container = document.createElement("div"); - container.addEventListener("click", async () => { - this.domainVerification(); - }); + const span = document.createElement("span"); + span.classList.add("conImg", "svgicon"); + span.style.setProperty("mask", `url("/icons/domain.svg")`); + container.append(span); + + container.addEventListener("click", async () => { + this.domainVerification(); + }); + + connectionContainer.appendChild(container); - connectionContainer.appendChild(container); - } serverConnections .filter((_) => actConMap.has(_)) .forEach((_) => { From 80e8eb8a429d2f9d7ff548db6285b6ee46826d0e Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 18:05:22 -0500 Subject: [PATCH 09/45] rid of weird shading --- 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 ec4974e..1e5cf18 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) ${100 - per}%)`; + aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${per}%)`; } aarea.onclick = () => check.click(); pollbody.append(aarea); From 875b3fb17da0eaf4318966d20f0ecabef8476311 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 18:38:55 -0500 Subject: [PATCH 10/45] show results of poll --- 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 1e5cf18..5f852e2 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1321,7 +1321,7 @@ class Message extends SnowFlake { question.textContent = this.poll.question.text; pollbody.append(question); let ccount = [...r.values()].reduce((e, l) => e + +l.me_voted, 0); - if (this.poll.allow_multiselect) voted = !!ccount; + voted = !!ccount; for (const a of this.poll.answers) { const aarea = document.createElement("div"); aarea.classList.add("flexltr", "answerArea"); From f0370b16102761dee40ec3d4cea75b4ad6fc723f Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 00:26:03 +0000 Subject: [PATCH 11/45] feat: mutual guilds box be the same size as mutual friends Signed-off-by: lizzie --- src/webpage/style.css | 16 +--------------- src/webpage/user.ts | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 506c62c..526fd05 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -19,19 +19,6 @@ body { background: color-mix(in srgb, var(--green), 20% white); } } -.mutFriends { - display: flex; - flex-direction: column; - align-items: stretch; - - .createdWebhook { - flex-grow: 1; - background: var(--primary-bg); - margin-bottom: 6px; - width: 100%; - box-sizing: border-box; - } -} .createdWebhook { display: flex; align-items: center; @@ -4345,12 +4332,11 @@ fieldset input[type="radio"] { .traceBars { padding: 10px 0px; } -.mutGuildBox { +.mutGuildBox, .mutFriends { background: var(--primary-bg); padding: 6px; cursor: pointer; margin-top: 6px; - img { width: 48px !important; height: 48px !important; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 867768b..9282629 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -803,7 +803,7 @@ class User extends SnowFlake { createWidget(guild?: Guild) { guild = this.localuser.guilds.get("@me") as Guild; const div = document.createElement("div"); - div.classList.add("flexltr", "createdWebhook"); + div.classList.add("mutFriends", "flexltr"); //TODO make sure this is something I can actually do here const name = document.createElement("b"); name.textContent = this.name; From b228c93c160c5e94f4bad61e9144909f8ffbc8bb Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 00:29:46 +0000 Subject: [PATCH 12/45] fix: vertical alignment of links on user profile 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 506c62c..2acd07a 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -4458,6 +4458,7 @@ fieldset input[type="radio"] { } .conProfDiv { margin: 6px; + align-items: center; } .disabled .conImg { From 9168eb1c4548cccaa19ef9d78cde13672dc49a3e Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Wed, 29 Jul 2026 19:49:00 -0500 Subject: [PATCH 13/45] lets try 100 --- src/webpage/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts index a5359ec..490289a 100644 --- a/src/webpage/hover.ts +++ b/src/webpage/hover.ts @@ -49,7 +49,7 @@ class Hover { this.elm2 = await this.makeHover(elm); Hover.bound = elm; Hover.watchForGone(); - }, 300); + }, 100); }); elm.addEventListener("mouseout", () => { clearTimeout(timeOut); From 42e0aa381d9e35c318e2231a56183874dfdc1611 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 01:11:19 +0000 Subject: [PATCH 14/45] feat: softer banner shadow drop, text shadow to make more readable text on white bgs Signed-off-by: lizzie --- src/webpage/localuser.ts | 2 +- src/webpage/style.css | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index aea01b7..2e890c1 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1785,7 +1785,7 @@ class Localuser { //https://cdn.discordapp.com/banners/677271830838640680/fab8570de5bb51365ba8f36d7d3627ae.webp?size=240 banner.style.setProperty( "background-image", - `linear-gradient(rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 40%), url(${this.info.cdn}/banners/${guild.id}/${guild.banner + new CDNParams({expectedSize: 128})})`, + `linear-gradient(rgba(80, 80, 80, 1) 0%, rgba(80, 80, 80, 0) 40%), url(${this.info.cdn}/banners/${guild.id}/${guild.banner + new CDNParams({expectedSize: 128})})`, ); banner.classList.add("Banner"); //background-image: diff --git a/src/webpage/style.css b/src/webpage/style.css index 506c62c..e21603c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2086,10 +2086,13 @@ span.instanceStatus { user-select: none; } .Banner { - height: 100px; + height: 8em; align-items: start; padding-top: 10px; background-size: cover; + .ellipsis { + text-shadow: 0px 0px 8px black; + } } #serverName { font-size: 1rem; From 0cd7df335de7a32575f70d2b6e2e4b13d7dbbd5d Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 03:29:49 +0000 Subject: [PATCH 15/45] feat: better folder styling Signed-off-by: lizzie --- src/webpage/localuser.ts | 4 ++-- src/webpage/style.css | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index aea01b7..bdb307d 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -2058,8 +2058,8 @@ class Localuser { menu.bindContextmenu(iconDiv); if (folder.color !== null && folder.color !== undefined) { - icon.style.setProperty("--folder-color", "#" + folder.color.toString(16).padStart(6, "0")); - if (!folder.color && folder.color !== 0) icon.style.removeProperty("--folder-color"); + folderDiv.style.setProperty("--folder-color", "#" + folder.color.toString(16).padStart(6, "0")); + if (!folder.color && folder.color !== 0) folderDiv.style.removeProperty("--folder-color"); } iconDiv.append(icon); const divy = document.createElement("div"); diff --git a/src/webpage/style.css b/src/webpage/style.css index 506c62c..ce1cffb 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1953,14 +1953,13 @@ span.instanceStatus { display: flex; justify-content: center; align-items: center; - background: var(--folder-bg); border-radius: 16px; margin: 2px; margin-bottom: 8px; cursor: pointer; } .folder-div { - background: color-mix(in srgb, var(--primary-text-soft), 70% transparent); + background: color-mix(in srgb, var(--folder-color, var(--accent-color)), 70% transparent); padding: 3px; margin: -3px; border-radius: 16px; From c750210eb59b23d8644ec50c0abf8911622f4e52 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 03:36:31 +0000 Subject: [PATCH 16/45] feat: make servers guild colour slightly darker than docked bg 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 2b72991..e373477 100644 --- a/src/webpage/themes.css +++ b/src/webpage/themes.css @@ -24,7 +24,7 @@ --primary-text-soft: #adb8b9; --secondary-bg: #16191b; --secondary-hover: #252b2c; - --servers-bg: #191c1d; + --servers-bg: #151718; --channels-bg: #2a2d33; --channel-selected: #3c4046; --typebox-bg: #3a3e45; From ea1b745bf4c3206208790fdcdaeb640e1a2a32a7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 06:35:06 +0000 Subject: [PATCH 17/45] feat: open attachments in new tab Signed-off-by: lizzie --- src/webpage/file.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webpage/file.ts b/src/webpage/file.ts index bd44b50..caace22 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -288,6 +288,8 @@ class File { if (src) { const a = document.createElement("a"); a.href = src; + a.target = "_blank"; + a.rel = "noopener noreferrer"; if (url) url.then((_) => { a.href = _; From d891869709d089432d2070b1af5e7123ecf03172 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 06:46:35 +0000 Subject: [PATCH 18/45] feat: proper i18n translated SI size suffixes Signed-off-by: lizzie --- src/webpage/file.ts | 9 ++++++--- translations/en.json | 6 ++++++ translations/qqq.json | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/webpage/file.ts b/src/webpage/file.ts index bd44b50..75a8374 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -309,11 +309,14 @@ class File { return div; } static filesizehuman(fsize: number) { - const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + // These DO change between languages, for example in russian it uses cyrillic script + // also NOBODY is uploading TBs of files... seriously no + // And finally we are using SI units, so we go in thousands :) + const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1000)); return ( - Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + + Number((fsize / Math.pow(1000, i)).toFixed(2)) * 1 + " " + - ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong + [I18n.filesize.B(), I18n.filesize.KB(), I18n.filesize.MB(), I18n.filesize.GB()][i] ); } } diff --git a/translations/en.json b/translations/en.json index 2b09e06..fc08a83 100644 --- a/translations/en.json +++ b/translations/en.json @@ -13,6 +13,12 @@ "whatelse": "What else do you think should happen?", "whereever": "Wherever this is" }, + "filesize": { + "B": "B", + "KB": "KB", + "MB": "MB", + "GB": "GB" + }, "@metadata": { "authors": ["MathMan05, TheGeekn°72"], "comment": "Don't know how often I'll update this top part lol // Aye, I'll take care of it", diff --git a/translations/qqq.json b/translations/qqq.json index 1ac7ca8..9eb1ea0 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -25,6 +25,12 @@ "whatelse": "Easter egg alert message shown randomly when clicking the 'Wherever this is' link on the 404 page. It's meant to be humorous/silly.", "whereever": "Link text on the 404 page that triggers random easter egg events when clicked." }, + "filesize": { + "B": "SI unit bytes abbreviated", + "KB": "SI unit kilobytes abbreviated", + "MB": "SI unit megabytes abbreviated", + "GB": "SI unit gigabytes abbreviated" + }, "onboarding": { "name": "Settings section title for the guild onboarding feature.", "disable": "Button label to disable the onboarding feature for a guild.", From 5285c071519d72f400a0e57c4d5d529ec7a26500 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 07:41:25 +0000 Subject: [PATCH 19/45] feat: add java, kotlin as supported languages Signed-off-by: lizzie --- src/webpage/highlighter/clike/langs.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json index 1c1b162..31b3461 100644 --- a/src/webpage/highlighter/clike/langs.json +++ b/src/webpage/highlighter/clike/langs.json @@ -1,6 +1,6 @@ [ { - "names":["js","ts","javascript"], + "names":["js","ts","typescript","javascript","tscript","jscript"], "keywords": ["break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do","else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield", "let", "await", "true", "false", "null", "undefined"], "firstLineShebang": true, "doubleSlashComments": true, @@ -8,14 +8,14 @@ "JSLikeTemplateStrings": true }, { - "names":["h","c","hc"], + "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"], "hashComments": true, "doubleSlashComments": true, "multilineSlashComments": true }, { - "names":["c++","cpp","hpp","cxx","hxx","h++"], + "names":["cplusplus","hplusplus","c++","cpp","hpp","cxx","hxx","h++"], "keywords":["alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "contract_assert", "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq", "final", "override", "transaction_safe", "transaction_safe_dynamic", "import", "module", "pre", "post"], "hashComments": true, "doubleSlashComments": true, @@ -52,5 +52,21 @@ "keywords": ["False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield"], "hashComments": true, "multiLine":"\"\"\"" - } +}, +{ + "names": ["java", "jav", "jv"], + "keywords": ["abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while"], + "firstLineShebang":true, + "doubleSlashComments": true, + "multilineSlashComments": true, + "multiLine":"\"\"\"" +}, +{ + "names": ["kt","kot","kotlin","kts"], + "keywords": ["as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while", "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", "param", "property", "receiver", "set", "setparam", "value", "where", "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg"], + "firstLineShebang":true, + "doubleSlashComments": true, + "multilineSlashComments": true, + "multiLine":"\"\"\"" +} ] From f9710e53344a5306026fd572a297198d3ef0c6a0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 07:44:21 +0000 Subject: [PATCH 20/45] may as well add glslang --- src/webpage/highlighter/clike/langs.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json index 31b3461..4493555 100644 --- a/src/webpage/highlighter/clike/langs.json +++ b/src/webpage/highlighter/clike/langs.json @@ -61,6 +61,13 @@ "multilineSlashComments": true, "multiLine":"\"\"\"" }, +{ + "names": ["glsl","glslang","opengl","glslc"], + "keywords": ["const", "uniform", "buffer", "shared", "attribute", "varying", "coherent", "volatile", "restrict", "readonly", "writeonly", "atomic_uint", "layout", "centroid", "flat", "smooth", "noperspective", "patch", "sample", "invariant", "precise", "break", "continue", "do", "for", "while", "switch", "case", "default", "if", "else", "subroutine", "in", "out", "inout", "int", "void", "bool", "true", "false", "float", "double", "discard", "return", "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", "uint", "uvec2", "uvec3", "uvec4", "dvec2", "dvec3", "dvec4", "mat2", "mat3", "mat4", "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4", "dmat2", "dmat3", "dmat4", "dmat2x2", "dmat2x3", "dmat2x4", "dmat3x2", "dmat3x3", "dmat3x4", "dmat4x2", "dmat4x3", "dmat4x4", "lowp", "mediump", "highp", "precision", "sampler1D", "sampler1DShadow", "sampler1DArray", "sampler1DArrayShadow", "isampler1D", "isampler1DArray", "usampler1D", "usampler1DArray", "sampler2D", "sampler2DShadow", "sampler2DArray", "sampler2DArrayShadow", "isampler2D", "isampler2DArray", "usampler2D", "usampler2DArray", "sampler2DRect", "sampler2DRectShadow", "isampler2DRect", "usampler2DRect", "sampler2DMS", "isampler2DMS", "usampler2DMS", "sampler2DMSArray", "isampler2DMSArray", "usampler2DMSArray", "sampler3D", "isampler3D", "usampler3D", "samplerCube", "samplerCubeShadow", "isamplerCube", "usamplerCube", "samplerCubeArray", "samplerCubeArrayShadow", "isamplerCubeArray", "usamplerCubeArray", "samplerBuffer", "isamplerBuffer", "usamplerBuffer", "image1D", "iimage1D", "uimage1D", "image1DArray", "iimage1DArray", "uimage1DArray", "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2DRect", "iimage2DRect", "uimage2DRect", "image2DMS", "iimage2DMS", "uimage2DMS", "image2DMSArray", "iimage2DMSArray", "uimage2DMSArray", "image3D", "iimage3D", "uimage3D", "imageCube", "iimageCube", "uimageCube", "imageCubeArray", "iimageCubeArray", "uimageCubeArray", "imageBuffer", "iimageBuffer", "uimageBuffer", "struct", "In", "addition,", "when", "targeting", "Vulkan,", "the", "following", "keywords", "also", "exist:", "texture1D", "texture1DArray", "itexture1D", "itexture1DArray", "utexture1D", "utexture1DArray", "texture2D", "texture2DArray", "itexture2D", "itexture2DArray", "utexture2D", "utexture2DArray", "texture2DRect", "itexture2DRect", "utexture2DRect", "texture2DMS", "itexture2DMS", "utexture2DMS", "texture2DMSArray", "itexture2DMSArray", "utexture2DMSArray", "texture3D", "itexture3D", "utexture3D", "textureCube", "itextureCube", "utextureCube", "textureCubeArray", "itextureCubeArray", "utextureCubeArray", "textureBuffer", "itextureBuffer", "utextureBuffer", "sampler", "samplerShadow", "subpassInput", "isubpassInput", "usubpassInput", "subpassInputMS", "isubpassInputMS", "usubpassInputMS", "The", "following", "are", "the", "keywords", "reserved", "for", "future", "use.", "Using", "them", "will", "result", "in", "a", "compile-time", "error:", "common", "partition", "active", "asm", "class", "union", "enum", "typedef", "template", "this", "resource", "goto", "inline", "noinline", "public", "static", "extern", "external", "interface", "long", "short", "half", "fixed", "unsigned", "superp", "input", "output", "hvec2", "hvec3", "hvec4", "fvec2", "fvec3", "fvec4", "filter", "sizeof", "cast", "namespace", "using", "sampler3DRect"], + "hashComments": true, + "doubleSlashComments": true, + "multilineSlashComments": true +}, { "names": ["kt","kot","kotlin","kts"], "keywords": ["as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while", "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", "param", "property", "receiver", "set", "setparam", "value", "where", "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg"], From ae3cdd27d3f410c923763dde0e4aef574804f73e Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 07:48:15 +0000 Subject: [PATCH 21/45] Mayyy as well add HLSL Signed-off-by: lizzie --- src/webpage/highlighter/clike/langs.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/webpage/highlighter/clike/langs.json b/src/webpage/highlighter/clike/langs.json index 4493555..40b8743 100644 --- a/src/webpage/highlighter/clike/langs.json +++ b/src/webpage/highlighter/clike/langs.json @@ -68,6 +68,16 @@ "doubleSlashComments": true, "multilineSlashComments": true }, +{ + "names": ["hlsl","hlslang","hlslc","directx"], + "keywords": [ + "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", + "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", "VertexShader", "void", "volatile", "while", "uint", "uint1", "uint2", "uint3", "uint4", "uint1x1", "uint1x2", "uint1x3", "uint1x4 uint2x1", "uint2x2", "uint2x3", "uint2x4 uint3x1", "uint3x2", "uint3x3", "uint3x4 uint4x1", "uint4x2", "uint4x3", "uint4x4", "bool", "bool1", "bool2", "bool3", "bool4", "bool1x1", "bool1x2", "bool1x3", "bool1x4 bool2x1", "bool2x2", "bool2x3", "bool2x4 bool3x1", "bool3x2", "bool3x3", "bool3x4 bool4x1", "bool4x2", "bool4x3", "bool4x4", "int", "int1", "int2", "int3", "int4", "int1x1", "int1x2", "int1x3", "int1x4 int2x1", "int2x2", "int2x3", "int2x4 int3x1", "int3x2", "int3x3", "int3x4 int4x1", "int4x2", "int4x3", "int4x4", "float", "float1", "float2", "float3", "float4", "float1x1", "float1x2", "float1x3", "float1x4 float2x1", "float2x2", "float2x3", "float2x4 float3x1", "float3x2", "float3x3", "float3x4 float4x1", "float4x2", "float4x3", "float4x4" + ], + "hashComments": true, + "doubleSlashComments": true, + "multilineSlashComments": true +}, { "names": ["kt","kot","kotlin","kts"], "keywords": ["as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while", "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", "param", "property", "receiver", "set", "setparam", "value", "where", "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg"], From 546f05740559e754f843b2a7e917a5d4f6027d75 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 08:12:00 +0000 Subject: [PATCH 22/45] feat: actually fetch verify_key for applications Signed-off-by: lizzie --- src/webpage/oauth2/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/oauth2/auth.ts b/src/webpage/oauth2/auth.ts index 7308049..911aa1a 100644 --- a/src/webpage/oauth2/auth.ts +++ b/src/webpage/oauth2/auth.ts @@ -29,7 +29,7 @@ type botjsonfetch = { guild_id: null | string; bot_public: boolean; bot_require_code_grant: boolean; - verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P + verify_key: string; //bot token flags: number; }; bot: { From 238ced3092b1da5a75ce547c704a0072942c8376 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 08:13:00 +0000 Subject: [PATCH 23/45] NOT BOT TOKEN --- src/webpage/oauth2/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/oauth2/auth.ts b/src/webpage/oauth2/auth.ts index 911aa1a..f17b441 100644 --- a/src/webpage/oauth2/auth.ts +++ b/src/webpage/oauth2/auth.ts @@ -29,7 +29,7 @@ type botjsonfetch = { guild_id: null | string; bot_public: boolean; bot_require_code_grant: boolean; - verify_key: string; //bot token + verify_key: string; //application client secret flags: number; }; bot: { From d1d21eec798dfb8341200799d37bdc9e6f897e07 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 30 Jul 2026 14:24:20 +0200 Subject: [PATCH 24/45] Localisation updates from https://translatewiki.net. --- translations/fr.json | 35 ++++++++++++++++++++++++++--------- translations/it.json | 15 ++++++++++++--- translations/ko.json | 5 +++++ translations/lb.json | 1 + translations/nl.json | 12 ++++++++++++ translations/sk.json | 3 +++ translations/zh-hans.json | 20 +++++++++++++++++--- 7 files changed, 76 insertions(+), 15 deletions(-) diff --git a/translations/fr.json b/translations/fr.json index 670fcfb..740b33c 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -52,6 +52,7 @@ "enable": "Activer l'introduction", "title": "Bienvenue sur $1 !" }, + "copyMedia": "Copier L'URL du média", "DMs": { "add": "Ajouter quelqu'un à la discussion privée (MP)", "close": "Fermer la discussion par messages privés", @@ -123,7 +124,7 @@ "hypesquad_house_1": "Audace", "hypesquad_house_2": "Excellence", "hypesquad_house_3": "Équilibre", - "legacy_username": "a un nom d'utilisateur d'origine", + "legacy_username": "a un nom d'utilisateur hérité", "partner": "Partenaire de l'instance", "premium": "Premium", "quest_completed": "a fait une quête", @@ -149,17 +150,17 @@ "TimeOutCool": "Exclu jusqu'à : $1", "allowIcons": "Autoriser les icônes de chaîne personnalisées", "announcement": "Annonces", - "copyId": "Copier l'id du canal", - "copyIdCat": "Copier l'id de la catégorie", + "copyId": "Copier l'identifiant de la chaîne", + "copyIdCat": "Copier l'identifiant de la catégorie", "createCatagory": "Créer une catégorie", - "createChannel": "Créer un canal", - "creating": "Création du canal", + "createChannel": "Créer un chaîne", + "creating": "Création du Chaîne", "delete": "Supprimer la chaîne", "deleteCat": "Supprimer la catégorie", "icon": "Icône :", "makeInvite": "Créer une invitation", "markRead": "Marquer comme lu", - "mute": "Rendre le canal muet", + "mute": "Rendre la chaîne muette", "name": "Chaîne", "name:": "Nom de la Chaîne :", "nsfw:": "Avertissement contenu mature :", @@ -268,7 +269,7 @@ "region": "Région : $1", "title": "Gérer les sessions" }, - "discovery": "Portail de Découverte (Discovery)", + "discovery": "Portail de Découverte", "dms": "Messages privés", "edit": "Modifier", "emoji": { @@ -330,7 +331,7 @@ "guild": { "COMMUNITY": "Demander à rejoindre", "disableInvites": "Désactiver les invitations :", - "DISCOVERABLE": "Visible depuis le Portail de Découverte (Discovery)", + "DISCOVERABLE": "Visible depuis le Portail de Découverte", "INVITES_DISABLED": "Sur invitation uniquement", "adminMenu": { "changePerms": "Modifier les permissions pour rechercher", @@ -457,7 +458,7 @@ "userField": "Nom d’utilisateur :", "welcomeJank": "Bienvenue dans le client Fermi" }, - "incorrectURLS": "## Cette instance a probablement envoyé des liens incorrects.\n### Si vous êtes le propriétaire de l’instance, veuillez consulter la section *Connecting from remote machines* de [cette page](https://docs.spacebar.chat/setup/server/) pour corriger le problème.\n Souhaitez-vous que le client Fermi tente automatiquement de corriger cette erreur pour vous permettre de vous connecter à l’instance ?", + "incorrectURLS": "## Cette instance a probablement envoyé des liens incorrects.\n### Si vous êtes le propriétaire de l’instance, veuillez consulter la section *Connecting from remote machines* de [cette page](https://docs.melodychat.org/setup/server/) pour corriger le problème.\nSouhaitez-vous que le client Fermi tente automatiquement de corriger cette erreur pour vous permettre de vous connecter à l’instance ?", "instInfo": "Informations d’instance", "instanceInfo": { "contact": "Envoyer un e-mail aux administrateurs de l’instance", @@ -486,6 +487,7 @@ "invite": { "accept": "Accepter", "alreadyJoined": "Déjà rejoint", + "joining": "Rejoindre...", "channel:": "Canal :", "createInvite": "Créer une invitation", "createdAt": "Créée le : $1", @@ -1011,6 +1013,21 @@ "resolving": "résolution de l'utilisateur", "unknown": "@utilisateur-inconnu" }, + "editMode": { + "editMsg": "Échapper à $1cancel$1" + }, + "keyboard": { + "shortcuts": "Raccourcis", + "empty": "", + "descs": { + "gifSearch": "Rechercher des GIFs :" + } + }, + "domain": { + "title": "Vérifier le domaine", + "domain": "Domaine :", + "dnsinst": "1. Connectez-vous à votre fournisseur DNS\n\n2. Créer un registre DNS : \n\nNom :\n```\n_harmony.$1\n```\n\nType :\n```\nTXT\n```\n\nContenu :\n```\n$2\n```\n\n-# Il faudra probablement quelques minutes pour le DNS se propage." + }, "vc": { "joinForStream": "Rejoignez le canal vocal pour regarder", "joiningStream": "Connexion à la diffusion en cours…", diff --git a/translations/it.json b/translations/it.json index 5b899f9..2cc88c3 100644 --- a/translations/it.json +++ b/translations/it.json @@ -32,6 +32,7 @@ "desc": "Descrizione:", "title": "Benvenuto a $1!" }, + "copyMedia": "Copia URL del contenuto multimediale", "DMs": { "add": "Aggiungi qualcuno a questo DM", "close": "Chiudi DM", @@ -149,7 +150,7 @@ "text": "Testo", "timedOutUntil": "In time-out fino: $1", "topic:": "Argomento:", - "typebox": "Messaggio in $1", + "typebox": "Messaggio in # $1", "unmute": "Riattiva canale", "voice": "Voce", "deleteThread": "Elimina thread", @@ -594,14 +595,16 @@ "PIN_MESSAGES": "Consente ai membri del ruolo di fissare messaggi.", "MANAGE_EVENTS": "Consente ai membri del ruolo di modificare/cancellare eventi (esistenti e futuri).", "SEND_VOICE_MESSAGES": "Consente l'invio di messaggi vocali nei canali di testo.", - "CREATE_INSTANT_INVITE": "Consente ai membri del ruolo di creare inviti per la gilda." + "CREATE_INSTANT_INVITE": "Consente ai membri del ruolo di creare inviti per la gilda.", + "SET_VOICE_CHANNEL_STATUS": "Imposta lo stato del canale vocale" }, "readableNames": { "BYPASS_SLOWMODE": "Bypassa la slowmode", "CONNECT": "Connetti", "PIN_MESSAGES": "Fissa messaggi", "SEND_MESSAGES": "Invia messaggi", - "STREAM": "Video" + "STREAM": "Video", + "SET_VOICE_CHANNEL_STATUS": "Imposta lo stato del canale vocale" } }, "profile": { @@ -692,6 +695,12 @@ "userping": { "unknown": "@utente-sconosciuto" }, + "keyboard": { + "shortcuts": "Tasti di scelta rapida", + "descs": { + "gifSearch": "Ricerca GIF:" + } + }, "domain": { "title": "Verifica dominio", "domain": "Dominio:" diff --git a/translations/ko.json b/translations/ko.json index 245488e..e2ad31f 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -34,6 +34,7 @@ "enable": "온보딩 활성화", "title": "$1에 오신 것을 환영합니다!" }, + "copyMedia": "미디어 URL 복사", "DMs": { "add": "이 메시지에 누군가를 추가", "close": "DM 닫기", @@ -468,6 +469,7 @@ "invite": { "accept": "수락", "alreadyJoined": "이미 참여했습니다", + "joining": "가입하는 중...", "channel:": "채널:", "createInvite": "초대장 만들기", "createdAt": "만든 날짜: $1", @@ -993,6 +995,9 @@ "resolving": "사용자 해결 중", "unknown": "@unknown-user" }, + "keyboard": { + "shortcuts": "단축키" + }, "vc": { "joinForStream": "음성 채널에 참여해 시청", "joiningStream": "스트리밍에 참여 중...", diff --git a/translations/lb.json b/translations/lb.json index 11db942..2ead5b2 100644 --- a/translations/lb.json +++ b/translations/lb.json @@ -17,6 +17,7 @@ "desc": "Beschreiwung:", "title": "Wëllkomm op $1!" }, + "copyMedia": "Medien-URL kopéieren", "DMs": { "markRead": "Als gelies markéieren" }, diff --git a/translations/nl.json b/translations/nl.json index 8778932..c3e5777 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -31,6 +31,7 @@ "enable": "Inwerken inschakelen", "title": "Welkom bij $1!" }, + "copyMedia": "Media-url kopiëren", "DMs": { "add": "Iemand aan dit directe bericht toevoegen", "close": "Directe bericht sluiten", @@ -465,6 +466,7 @@ "invite": { "accept": "Accepteren", "alreadyJoined": "Al lid", + "joining": "Deelnemen...", "channel:": "Kanaal:", "createInvite": "Uitnodiging maken", "createdAt": "Gemaakt op $1", @@ -990,6 +992,16 @@ "resolving": "gebruiken aan het opzoeken", "unknown": "@onbekende-gebruiker" }, + "editMode": { + "editMsg": "Escape om te $1annuleren$1" + }, + "keyboard": { + "shortcuts": "Sneltoetsen", + "empty": "", + "descs": { + "gifSearch": "GIF's zoeken:" + } + }, "domain": { "title": "Domein verifiëren", "domain": "Domein:", diff --git a/translations/sk.json b/translations/sk.json index 205a31e..05fcdf0 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -988,6 +988,9 @@ "resolving": "zisťuje sa používateľ", "unknown": "@neznámy-používateľ" }, + "editMode": { + "editMsg": "Escapovať na $1cancel$1" + }, "domain": { "title": "Overiť doménu", "domain": "Doména:", diff --git a/translations/zh-hans.json b/translations/zh-hans.json index d236b15..785f913 100644 --- a/translations/zh-hans.json +++ b/translations/zh-hans.json @@ -39,6 +39,7 @@ "enable": "启用入门", "title": "欢迎来到$1!" }, + "copyMedia": "复制媒体 URL", "DMs": { "add": "将某人添加到此私信", "close": "关闭私信", @@ -160,7 +161,7 @@ "text": "文字", "timedOutUntil": "超时至:$1", "topic:": "主题:", - "typebox": "在$1的消息", + "typebox": "在 # $1 的消息", "unmute": "取消静音频道", "voice": "语音", "deleteThread": "删除帖子", @@ -473,6 +474,7 @@ "invite": { "accept": "接受", "alreadyJoined": "已加入", + "joining": "正在加入…", "channel:": "频道:", "createInvite": "创建邀请", "createdAt": "创建于 $1", @@ -748,7 +750,8 @@ "REQUEST_TO_SPEAK": "允许该身份组成员请求在表演频道发言。", "USE_EMBEDDED_ACTIVITIES": "允许该身份组成员使用嵌入式活动。", "USE_APPLICATION_COMMANDS": "允许该身份组成员使用应用程序命令。", - "USE_EXTERNAL_APPS": "允许该身份组成员以应用程序产生的回复在频道中公开显示(禁用此功能后,用户仍可使用其应用,但回复将仅他们自己可见。此功能仅适用于未安装到公会的应用程序)。" + "USE_EXTERNAL_APPS": "允许该身份组成员以应用程序产生的回复在频道中公开显示(禁用此功能后,用户仍可使用其应用,但回复将仅他们自己可见。此功能仅适用于未安装到公会的应用程序)。", + "SET_VOICE_CHANNEL_STATUS": "设置语音频道状态" }, "readableNames": { "ADD_REACTIONS": "添加反应", @@ -801,7 +804,8 @@ "VIEW_AUDIT_LOG": "查看审计日志", "VIEW_CHANNEL": "查看频道", "VIEW_CREATOR_MONETIZATION_ANALYTICS": "查看创建者变现分析", - "VIEW_GUILD_INSIGHTS": "查看公会洞见" + "VIEW_GUILD_INSIGHTS": "查看公会洞见", + "SET_VOICE_CHANNEL_STATUS": "设置语音频道状态" } }, "pinMessage": "固定消息", @@ -998,6 +1002,16 @@ "resolving": "正在解析用户", "unknown": "@未知用户" }, + "editMode": { + "editMsg": "按下“Esc”以$1取消$1" + }, + "keyboard": { + "shortcuts": "快捷键", + "empty": "<未设置键位绑定>", + "descs": { + "gifSearch": "搜索 GIF:" + } + }, "domain": { "title": "验证域名", "domain": "域名:", From 12e74a087c452bddac405424d13a354f36155f8a Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 18:33:38 +0000 Subject: [PATCH 25/45] feat: implement alpha decorations Signed-off-by: lizzie --- src/webpage/style.css | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 4070b15..9434a61 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -3481,15 +3481,17 @@ img.bigembedimg { width: 300px; max-height: 100svh; background: var(--card-bg); - box-shadow: - 0 0 8px var(--shadow), - inset 0 132px 64px var(--accent_color); + box-shadow: inset 0 0 300px 350px var(--accent_color); + opacity: 1; border-radius: 8px; overflow: hidden; align-items: flex-start; z-index: 3; display: flex; flex-direction: column; + .infosection { + background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent); + } &:not(.hypoprofile) { animation-duration: 0.2s; animation-name: fade-in; @@ -3502,7 +3504,7 @@ img.bigembedimg { margin-bottom: 10px; box-sizing: border-box; padding: 10px; - background: var(--secondary-bg); + background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent); border: none; border-radius: 5px; font-size: 14px; From 27c743b68084d3acda5f642ea5d1972a359b052a Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 30 Jul 2026 15:59:06 -0500 Subject: [PATCH 26/45] copy channel URL --- src/webpage/channel.ts | 11 +++++++++++ translations/en.json | 1 + 2 files changed, 12 insertions(+) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index ef9211e..e89608d 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -269,6 +269,17 @@ class Channel extends SnowFlake { ); this.contextmenu.addSeperator(); + this.contextmenu.addButton( + I18n.channel.copyURL(), + function (this: Channel) { + navigator.clipboard.writeText(`${location.origin}/channels/${this.guild.id}/${this.id}`); + }, + { + visible: function () { + return this.type !== 4; + }, + }, + ); //TODO copy ID icon this.contextmenu.addButton( function () { diff --git a/translations/en.json b/translations/en.json index fc08a83..23d5858 100644 --- a/translations/en.json +++ b/translations/en.json @@ -130,6 +130,7 @@ "cancel": "Cancel", "channel": { "SlowmodeCool": "Slowmode cooldown: $1", + "copyURL":"Copy channel URL", "TimeOutCool": "Timed out until: $1", "allowIcons": "Allow custom channel icons", "announcement": "Announcements", From f0e35ea4a8a145227d8af2f761c0cdc47c7bf9c4 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 30 Jul 2026 16:28:27 -0500 Subject: [PATCH 27/45] shorten channel name --- src/webpage/channel.ts | 21 ++++++++++++++------- src/webpage/localuser.ts | 7 +++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index e89608d..dd9c580 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -44,6 +44,10 @@ class Channel extends SnowFlake { owner: Guild; headers: Localuser["headers"]; name!: string; + get shortName() { + if (this.name.length > 50) return this.name.slice(0, 50) + "..."; + return this.name; + } parentId?: string; parent?: Channel; children!: Channel[]; @@ -410,7 +414,7 @@ class Channel extends SnowFlake { update(); const inviteOptions = new Dialog("", {noSubmit: true}); inviteOptions.options.addTitle(I18n.inviteOptions.title()); - inviteOptions.options.addText(I18n.invite.subtext(this.name, this.guild.properties.name)); + inviteOptions.options.addText(I18n.invite.subtext(this.shortName, this.guild.properties.name)); inviteOptions.options.addSelect( I18n.invite.expireAfter(), @@ -438,7 +442,7 @@ class Channel extends SnowFlake { inviteOptions.show(); } generateSettings() { - const settings = new Settings(I18n.channel.settingsFor(this.name)); + const settings = new Settings(I18n.channel.settingsFor(this.shortName)); { const gensettings = settings.addButton(I18n.channel.settings()); const form = gensettings.addForm("", () => {}, { @@ -1063,7 +1067,7 @@ class Channel extends SnowFlake { const myhtml = document.createElement("p2"); myhtml.classList.add("ellipsis"); - myhtml.textContent = this.name; + myhtml.textContent = this.shortName; this.nameSpan = new WeakRef(myhtml); decdiv.appendChild(myhtml); caps.appendChild(decdiv); @@ -1130,7 +1134,7 @@ class Channel extends SnowFlake { div.append(button); const myhtml = document.createElement("span"); myhtml.classList.add("ellipsis"); - myhtml.textContent = this.name; + myhtml.textContent = this.shortName; this.nameSpan = new WeakRef(myhtml); const decoration = this.renderIcon(); button.appendChild(decoration); @@ -2028,7 +2032,7 @@ class Channel extends SnowFlake { div.append(tags); const title = document.createElement("h3"); - title.textContent = new MarkDown(this.name).makeHTML().textContent; + title.textContent = new MarkDown(this.shortName).makeHTML().textContent; div.append(title); const member = @@ -2628,7 +2632,10 @@ class Channel extends SnowFlake { if (this.curCommand) { this.curCommand.render(typebox, this); } - typebox.style.setProperty("--channel-text", JSON.stringify(I18n.channel.typebox(this.name))); + typebox.style.setProperty( + "--channel-text", + JSON.stringify(I18n.channel.typebox(this.shortName)), + ); if (!this.curCommand && !this.isForum()) { const md = typebox.markdown; md.owner = this; @@ -2681,7 +2688,7 @@ class Channel extends SnowFlake { "/channels/" + this.guild.id + "/" + this.id + (aroundMessage ? `/${aroundMessage}` : ""), ); } - this.localuser.pageTitle("#" + this.name); + this.localuser.pageTitle("#" + this.shortName); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; if (this.topic) { channelTopic.innerHTML = ""; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 4eb3442..1cefaca 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -2058,7 +2058,10 @@ class Localuser { menu.bindContextmenu(iconDiv); if (folder.color !== null && folder.color !== undefined) { - folderDiv.style.setProperty("--folder-color", "#" + folder.color.toString(16).padStart(6, "0")); + folderDiv.style.setProperty( + "--folder-color", + "#" + folder.color.toString(16).padStart(6, "0"), + ); if (!folder.color && folder.color !== 0) folderDiv.style.removeProperty("--folder-color"); } iconDiv.append(icon); @@ -4695,7 +4698,7 @@ class Localuser { maybe.sort((a, b) => b[0] - a[0]); this.MDSearchOptions( maybe.map((a) => { - return {name: "# " + a[1].name, replace: `<#${a[1].id}> `}; + return {name: "# " + a[1].shortName, replace: `<#${a[1].id}> `}; }), original, box, From 0c622b93a51b34c91b771e784caf1f88a167ee1a Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 30 Jul 2026 16:30:20 -0500 Subject: [PATCH 28/45] don't let it overflow --- src/webpage/style.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 9434a61..f326edd 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2560,6 +2560,9 @@ span.instanceStatus { opacity: 0.5; position: absolute; cursor: text; + text-wrap: nowrap; + width: 100%; + overflow: hidden; } .outerTypeBox { max-height: 50svh; @@ -4336,7 +4339,8 @@ fieldset input[type="radio"] { .traceBars { padding: 10px 0px; } -.mutGuildBox, .mutFriends { +.mutGuildBox, +.mutFriends { background: var(--primary-bg); padding: 6px; cursor: pointer; From 30a12362d8e8a1180cfc425d08001ca8aaa0792a Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 30 Jul 2026 16:42:51 -0500 Subject: [PATCH 29/45] mutral guilds click event --- src/webpage/user.ts | 7 ++++++- translations/en.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 9282629..65c06d9 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -1447,7 +1447,7 @@ class User extends SnowFlake { }; (async () => { const high = await this.highInfo(); - const mut = buttons.add(I18n.profile.mut()); + const mut = buttons.add(I18n.profile.mut(high.mutual_guilds.length + "")); const mutDiv = document.createElement("div"); mutDiv.append( @@ -1466,6 +1466,11 @@ class User extends SnowFlake { gname.textContent = guild.properties.name; info.append(gname); box.append(icon, info); + box.onclick = () => { + removeAni(background); + guild.loadGuild(); + guild.loadChannel(); + }; if (nick) info.append(nick); return box; }) diff --git a/translations/en.json b/translations/en.json index 23d5858..567f277 100644 --- a/translations/en.json +++ b/translations/en.json @@ -810,7 +810,7 @@ "bio": "About me:", "joined": "Account made: $1", "joinedMember": "Joined $1: $2", - "mut": "Mutual guilds", + "mut": "Mutual guilds ($1)", "mutFriends": "Mutual friends", "permInfo": "Permissions", "userInfo": "User info", From 01e8b3802bc074d568b21de00674da14abe57202 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Thu, 30 Jul 2026 16:50:12 -0500 Subject: [PATCH 30/45] re-render reply box --- src/webpage/localuser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 1cefaca..7d7a182 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -4321,6 +4321,7 @@ class Localuser { replyingto: this.focusChannel.replyingto, }); this.focusChannel.replyingto = null; + this.focusChannel.makereplybox(); } } From 85977753ad4c175b35d01bea0feb5f31d2f832e6 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 30 Jul 2026 23:15:55 +0000 Subject: [PATCH 31/45] 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 9ab6a8b378f1bff76804786348d6e3921e57fcb0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 31 Jul 2026 22:02:14 +0000 Subject: [PATCH 32/45] 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 33/45] 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 34/45] 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 35/45] 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 8eba4f1b5973589286085ba399f20c2e4952c371 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 10:03:12 +0000 Subject: [PATCH 36/45] 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 37/45] 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 112bd2561c9e0dc2bb2f9faf7e06bd9eb53f2591 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 1 Aug 2026 16:58:24 +0000 Subject: [PATCH 38/45] 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 39/45] 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 40/45] 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 41/45] 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 42/45] 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 56b053788b2bc0373992e1ee279ee15fb4c473cf Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 2 Aug 2026 03:41:52 +0000 Subject: [PATCH 43/45] 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 44/45] 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 45/45] 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";