From e73feaeec09b84425d7fb58559e75e2711153b90 Mon Sep 17 00:00:00 2001 From: Mathium05 Date: Sun, 2 Aug 2026 15:48:09 -0500 Subject: [PATCH] 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();