make typebox into a seperate class for fewer bugs

This commit is contained in:
Mathium05
2026-08-02 15:48:09 -05:00
parent 56b053788b
commit e73feaeec0
5 changed files with 400 additions and 395 deletions
+23 -105
View File
@@ -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<Message | undefined> {
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<Blob, HTMLElement>();
textSave = "";
collectBox() {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const [files, html] = this.localuser.fileExtange([], new WeakMap<Blob, HTMLElement>());
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) {
+2 -8
View File
@@ -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<invitejson[]>(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) {
+5 -261
View File
@@ -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<string, string>();
//@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<void>((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<void> {
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<Blob, HTMLElement>();
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")!,
+7 -21
View File
@@ -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, HTMLElement>,
) => [Blob[], WeakMap<Blob, HTMLElement>];
MDSearchOptions(
options: MDSearchOption[],
original: string,
+363
View File
@@ -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<Blob, HTMLElement>();
private static pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement;
private static nonceMap = new Map<string, string>();
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<string, BoxState>();
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<void> {
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<void>((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();