Merge branch 'main' into pr-451

This commit is contained in:
Mathium05
2026-08-09 19:34:23 -05:00
32 changed files with 1235 additions and 649 deletions
+44 -109
View File
@@ -27,16 +27,16 @@ 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";
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,37 +2707,17 @@ class Channel extends SnowFlake {
}
this.rendertyping();
TypeBox.changeWrite();
try {
(document.getElementById("typebox") as HTMLDivElement).contentEditable = this.canMessage
? "plaintext-only"
: "false";
} catch {
(document.getElementById("typebox") as HTMLDivElement).contentEditable = this.canMessage
? "true"
: "false";
if (getMessages) await this.putmessages();
} catch (e) {
if (e instanceof Error) {
const d = new Dialog(e.message);
d.show();
}
return;
}
(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();
}
if (getMessages) await this.putmessages();
await prom;
if (id !== Channel.genid) {
@@ -2938,11 +2864,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;
}
+9 -1
View File
@@ -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) {
+2 -1
View File
@@ -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());
-2
View File
@@ -210,7 +210,6 @@ class Embed {
const div = document.createElement("div");
div.classList.add("messageimgdiv");
const img = createImg(this.json.thumbnail.proxy_url);
img.classList.add("messageimg");
img.onclick = () => {
const full = new ImagesDisplay([
new File(
@@ -442,7 +441,6 @@ class Embed {
video.controls = true;
video.preload = "metadata";
video.src = videoUrl;
video.classList.add("messageimg");
if (this.json.video?.width) {
let scale = 1;
const max = 96 * 3;
+14 -9
View File
@@ -6,7 +6,7 @@ import {emojijson, emojiSource} from "./jsontypes.js";
import {Localuser} from "./localuser.js";
import {BinRead} from "./utils/binaryUtils.js";
import {CDNParams} from "./utils/cdnParams.js";
import {removeAni} from "./utils/utils.js";
import {createImg, removeAni} from "./utils/utils.js";
//I need to recompile the emoji format for translation
class Emoji {
@@ -89,17 +89,20 @@ class Emoji {
getHTML(bigemoji: boolean = false, click = true) {
if (this.id) {
if (!this.owner) throw new Error("owner is missing for custom emoji!");
const emojiElem = document.createElement("img");
const emojiElem = createImg(
this.owner.info.cdn +
"/emojis/" +
this.id +
"." +
(this.animated ? "gif" : "png") +
new CDNParams({expectedSize: 32, animated: this.animated}),
undefined,
undefined,
"emoji",
);
emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin = "anonymous";
emojiElem.src =
this.owner.info.cdn +
"/emojis/" +
this.id +
"." +
(this.animated ? "gif" : "png") +
new CDNParams({expectedSize: 32, animated: this.animated});
emojiElem.alt = this.name;
emojiElem.loading = "lazy";
@@ -157,9 +160,11 @@ class Emoji {
guildText.classList.add("flexttb", "guildEmojiText");
const guildName = document.createElement("span");
guildName.classList.add("guildName");
guildName.textContent = lookup.guild.name;
const guildDesc = document.createElement("span");
guildDesc.classList.add("guildDesc");
const discoverable = lookup.guild.features.find((_) => _ === "DISCOVERABLE");
if (discoverable) {
if (lookup.guild.description) {
+10 -6
View File
@@ -44,7 +44,7 @@ class File {
this.content_type = fileJSON.content_type;
this.size = fileJSON.size;
}
getHTML(temp: boolean = false, fullScreen = false, OSpoiler = false, max = 96 * 3): HTMLElement {
getHTML(temp: boolean = false, fullScreen = false, OSpoiler = false, max = 96 * 3, gallery = false): HTMLElement {
function makeSpoilerHTML(): HTMLElement {
const spoil = document.createElement("div");
spoil.classList.add("fSpoil");
@@ -80,8 +80,10 @@ class File {
img.height = this.height;
}
if (!fullScreen) {
img.classList.add("messageimg");
div.classList.add("messageimgdiv");
if (gallery) {
div.classList.add("messagegallerydiv");
}
img.onclick = () => {
if (this.owner) {
const full = new ImagesDisplay(
@@ -106,12 +108,13 @@ class File {
img.setSrcs(src);
});
div.append(img);
if (this.width && !fullScreen) {
// Non-gallery images are sized according to their actual size
if (!gallery && this.width && !fullScreen) {
img.style.width = div.style.width = this.width + "px";
img.style.height = div.style.height = this.height + "px";
} else if (!fullScreen) {
img.style.maxWidth = div.style.maxWidth = 96 * 3 + "px";
img.style.maxHeight = div.style.maxHeight = 96 * 3 + "px";
} else if (!gallery && !fullScreen) {
img.style.maxWidth = div.style.maxWidth = max + "px";
img.style.maxHeight = div.style.maxHeight = max + "px";
}
img.isAnimated().then((animated) => {
if (!animated || !this.owner || fullScreen) return;
@@ -158,6 +161,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;
+10 -16
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, {
@@ -197,6 +198,14 @@ class Guild extends SnowFlake {
welcomeScreen?: welcomeScreen;
static readonly contextmenu = new Contextmenu<Guild, void>("guild menu");
static setupcontextmenu() {
Guild.contextmenu.addButton(
() => I18n.guild.markRead(),
function (this: Guild) {
this.markAsRead();
},
);
Guild.contextmenu.addSeperator();
Guild.contextmenu.addButton(
() => I18n.guild.makeInvite(),
function (this: Guild) {
@@ -211,14 +220,6 @@ class Guild extends SnowFlake {
color: "blue",
},
);
Guild.contextmenu.addSeperator();
Guild.contextmenu.addButton(
() => I18n.guild.markRead(),
function (this: Guild) {
this.markAsRead();
},
);
Guild.contextmenu.addButton(
() => I18n.guild.notifications(),
@@ -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) {
+8
View File
@@ -85,5 +85,13 @@
"doubleSlashComments": true,
"multilineSlashComments": true,
"multiLine":"\"\"\""
},
{
"names":["bash","sh","ksh","csh","shell"],
"keywords":["if","fi","else","then","while","do","for","continue","done","break","case","esac","in","local","return","declare","EOF","readonly","true","false"],
"firstLineShebang":true,
"hashComments": true,
"doubleSlashComments": false,
"multilineSlashComments": false
}
]
+1 -1
View File
@@ -25,7 +25,7 @@ function regex(config: clikeConf): RegExp {
`(${RegExp.escape(config.multiLine)}(?:${matchOpts.join("|")})*(?:${RegExp.escape(config.multiLine)})?)`,
);
}
conds.push(`\'(\\\\(.|\\n)|[^"\\n\\\\])*\'?`);
conds.push(`\'(\\\\(.|\\n)|[^\'\\n\\\\])*\'?`);
conds.push(`"(\\\\(.|\\n)|[^"\\n\\\\])*"?`);
if (config.hashComments) {
conds.push("#.*");
+4
View File
@@ -68,9 +68,13 @@ class Hover {
this.elm2.remove();
});
}
static prevDiv = new WeakRef(document.createElement("div"));
async makeHover(elm: HTMLElement) {
const prev = Hover.prevDiv.deref();
if (prev) prev.remove();
if (!document.contains(elm)) return document.createElement("div");
const div = document.createElement("div");
Hover.prevDiv = new WeakRef(div);
if (this.customHTML) {
div.append(this.customHTML());
+1
View File
@@ -0,0 +1 @@
<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-width="20" stroke-linecap="round" stroke-linejoin="round" paint-order="stroke markers fill"><path d="m91.18 42.4 17.38-18.02a34.78 34.78 0 1 1 49.18 49.18l-27.47 27.47a34.85 34.85 0 0 1-49.24-.06"/><path d="m93.98 138.48-18.96 17.7a34.85 34.85 0 0 1-49.24-.06 34.7 34.7 0 0 1 .06-49.12L53.3 79.53a34.8 34.8 0 0 1 49.18 0"/></g></svg>

After

Width:  |  Height:  |  Size: 434 B

+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")!,
+1 -1
View File
@@ -12,6 +12,7 @@ if (window.location.pathname.startsWith("/invite"))
}
console.log(m);
well = m.get(well.toLowerCase()) || (await getInstanceInfo(well))?.api || well;
well = well.replace(/\/*$/gm, "");
const joinable: Specialuser[] = [];
for (const key in users.users) {
@@ -20,7 +21,6 @@ if (window.location.pathname.startsWith("/invite"))
if (well && user.serverurls.wellknown.includes(well)) {
joinable.push(user);
}
console.log(user);
}
}
+26 -22
View File
@@ -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";
@@ -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) {
@@ -3305,6 +3305,24 @@ class Localuser {
AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()),
{defaultIndex: AnimateTristateValues.indexOf(prefs.animateIcons)},
);
animations.addSelect(
I18n.accessibility.playEmoji(),
async (i) => {
prefs.animateEmoji = AnimateTristateValues[i];
await setPreferences(prefs);
},
AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()),
{defaultIndex: AnimateTristateValues.indexOf(prefs.animateEmoji)},
);
animations.addSelect(
I18n.accessibility.playSticker(),
async (i) => {
prefs.animateSticker = AnimateTristateValues[i];
await setPreferences(prefs);
},
AnimateTristateValues.map((_) => I18n.accessibility.gifSettings[_]()),
{defaultIndex: AnimateTristateValues.indexOf(prefs.animateSticker)},
);
}
settings.addButton(I18n.localuser.security(), {head: true});
{
@@ -4280,21 +4298,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 +4538,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 +4549,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 +4584,6 @@ class Localuser {
);
}
}
fileExtange!: (
files: Blob[],
html: WeakMap<Blob, HTMLElement>,
) => [Blob[], WeakMap<Blob, HTMLElement>];
MDSearchOptions(
options: MDSearchOption[],
original: string,
+37 -34
View File
@@ -6,6 +6,7 @@ import {I18n} from "./i18n.js";
import {Dialog} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {highlight} from "./highlighter/index.js";
import {TypeBox} from "./typeBox.js";
const linkMenu = new Contextmenu<string, void>("copyLink", true);
linkMenu.addButton(
() => I18n.copyRegLink(),
@@ -14,7 +15,7 @@ linkMenu.addButton(
},
{group: "copyLink"},
);
const isFirefox = navigator.userAgent.toLowerCase().includes("firefox");
const isFirefox = Error.prototype.stack === "";
class MarkDown {
static emoji?: typeof Emoji;
txt: string[];
@@ -351,19 +352,20 @@ class MarkDown {
if (stdsize) {
build = build.replaceAll("\n", "");
}
if (find === count) {
if (find === count || (keep && count === 3)) {
appendcurrent();
i = j;
if (keep) {
build += "`".repeat(find);
}
if (build.endsWith("\n")) build += "\n";
if (count !== 3 && !stdsize) {
const samp = document.createElement("samp");
samp.textContent = build;
span.appendChild(samp);
} else {
const pre = document.createElement("pre");
if (build.at(-1) === "\n") {
if (build.at(-1) === "\n" && !isFirefox) {
build = build.substring(0, build.length - 1);
}
if (txt[i] === "\n") {
@@ -974,14 +976,20 @@ class MarkDown {
}
appendcurrent();
const last = getCurLast();
function addSpacer() {
const s = document.createElement("span");
s.style.setProperty("white-space", "pre");
s.textContent = "";
s.setAttribute("real", "");
span.append(s);
}
if (
last &&
last instanceof Text &&
last.textContent === "\n" &&
Error.prototype.stack === "" &&
!isFirefox
isFirefox &&
(last instanceof Text || last instanceof HTMLSpanElement) &&
last.textContent === "\n"
) {
addSpacer();
span.append(current);
}
if (
@@ -1052,17 +1060,6 @@ class MarkDown {
box.addEventListener("keydown", (_) => {
if (_.isComposing) return;
if (Error.prototype.stack !== "") return;
if (_.key === "Enter") {
const selection = window.getSelection() as Selection;
if (!selection) return;
const range = selection.getRangeAt(0);
const node = new Text("\n");
range.insertNode(node);
const g = node.nextSibling;
if (g) range.setStart(g, 0);
_.preventDefault();
return;
}
});
let prevcontent = "";
const gatherBoxContents = (isBackSpace: boolean) => {
@@ -1083,28 +1080,29 @@ class MarkDown {
const content = this.rawString;
if (content) {
let txti = text;
if (trim) {
if (trim && TypeBox.inPre()) {
txti = txti.replace(/\n$/, "");
}
const [_first, end] = content.split(txti);
console.log([txti, txt, end]);
const [first, ...ends] = content.split(txti);
const end = ends.join(txti);
if (rstr) {
const tw = text.split(rstr);
tw.pop();
text = tw.join("");
}
const boxText = txti + txt + (end ?? "");
box.textContent = boxText;
const boxText = first + txti + txt + (end ?? "");
box.innerHTML = "";
this.txt = boxText.split("");
box.append(this.makeHTML({keep: true}));
const len = txti.length + txt.length;
text = boxText;
this.txt = text.split("");
this.boxupdate(len, false, 0);
console.log(this.rawString);
} else {
box.textContent = txt;
this.txt = txt.split("");
box.innerHTML = "";
box.append(this.makeHTML({keep: true}));
text = txt;
this.txt = text.split("");
this.boxupdate(txt.length, false, 0);
}
};
@@ -1114,7 +1112,7 @@ class MarkDown {
gatherBoxContents(_.key === "Backspace");
};
box.onkeydown = (_) => {
if (isFirefox && _.key === "Enter" && !text.endsWith("\n")) {
if (isFirefox && _.key === "Enter" && (TypeBox.inPre() || _.shiftKey)) {
_.preventDefault();
_.stopImmediatePropagation();
@@ -1191,6 +1189,7 @@ class MarkDown {
html.childNodes[0].childNodes.length === 1 &&
html.childNodes[0].childNodes[0];
//console.log(box.cloneNode(true), html.cloneNode(true));
if (isFirefox) allowLazy = false;
//TODO this may be slow, may want to check in on this in the future if it is
if ((!box.hasChildNodes() || html.isEqualNode(Array.from(box.childNodes)[0])) && allowLazy) {
//console.log("no replace needed");
@@ -1233,6 +1232,11 @@ class MarkDown {
if (thing instanceof Text) {
const text = thing.textContent;
build += text;
if (element.tagName.toLowerCase() === "pre") {
if (build.endsWith("\n") && isFirefox) {
build = build.replace(/\n$/, "");
}
}
continue;
}
@@ -1431,13 +1435,13 @@ function saveCaretPosition(
build += node.textContent;
}
} else {
//console.error(node,"This shouldn't happen");
//console.error(node, "This shouldn't happen");
}
}
}
crawlForText(context);
if (baseString === "\n") {
build += baseString;
//build += baseString;
}
text = build;
len += build.length;
@@ -1447,15 +1451,14 @@ function saveCaretPosition(
len = Math.min(len, txtLengthFunc(context).length);
len += offset;
return function restore(backspace = false) {
return function restore(_backspace = false) {
if (!selection) return;
const pos = getTextNodeAtPosition(context, len, txtLengthFunc);
if (
pos.node instanceof Text &&
pos.node.textContent === "\n" &&
pos.node.nextSibling &&
Error.prototype.stack === "" &&
!backspace
isFirefox
) {
if (pos.node.nextSibling instanceof Text && pos.node.nextSibling.textContent === "\n") {
pos.position = 1;
+10 -6
View File
@@ -28,6 +28,7 @@ import {ImagesDisplay} from "./disimg";
import {ReportMenu} from "./reporting/report.js";
import {getDeveloperSettings} from "./utils/storage/devSettings.js";
import {getPreferences} from "./utils/storage/userPreferences.js";
import {TypeBox} from "./typeBox.js";
class Message extends SnowFlake {
static contextmenu = new Contextmenu<Message, void>("message menu");
stickers!: Sticker[];
@@ -193,7 +194,9 @@ class Message extends SnowFlake {
);
},
{
//TODO make icon
icon: {
css: "svg-link",
},
},
);
Message.contextmenu.addButton(
@@ -1015,7 +1018,7 @@ class Message extends SnowFlake {
area.append(md.makeHTML());
area.addEventListener("keyup", (event) => {
if (this.localuser.keyup(event)) return;
if (event.key === "Enter" && !event.shiftKey) {
if (event.key === "Enter" && !event.shiftKey && !TypeBox.inPre()) {
this.edit(MarkDown.gatherBoxText(area));
this.channel.editing = null;
this.generateMessage();
@@ -1023,7 +1026,7 @@ class Message extends SnowFlake {
});
area.addEventListener("keydown", (event) => {
this.localuser.keydown(event);
if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
if (event.key === "Enter" && !event.shiftKey && !TypeBox.inPre()) event.preventDefault();
if (event.key === "Escape") {
this.channel.editing = null;
this.generateMessage();
@@ -1080,8 +1083,9 @@ class Message extends SnowFlake {
if (this.attachments.length) {
const attach = document.createElement("div");
attach.classList.add("flexltr", "attachments");
const isGallery = this.attachments.length > 1;
for (const thing of this.attachments) {
attach.appendChild(thing.getHTML());
attach.appendChild(thing.getHTML(false, false, false, 96 * 3, isGallery));
}
messagedwrap.appendChild(attach);
}
@@ -1291,7 +1295,7 @@ class Message extends SnowFlake {
for (const sticker of this.stickers) {
stickerArea.append(sticker.getHTML());
}
div.append(stickerArea);
text.append(stickerArea);
if (this.poll) {
const pollbody = document.createElement("div");
@@ -1361,7 +1365,7 @@ class Message extends SnowFlake {
count.textContent = I18n.poll.count("" + c, per + "");
aarea.append(count);
if (per)
aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${per}%)`;
aarea.style.background = `linear-gradient(to right, var(--accent-color) ${per}%, var(--bg) ${per}%)`;
}
aarea.onclick = () => check.click();
pollbody.append(aarea);
+11
View File
@@ -20,6 +20,17 @@ export class NotificationHandler {
noticontent ||= message.embeds[0]?.json.title;
noticontent ||= message.content.textContent;
}
if (message.attachments.length > 0) {
noticontent ||= I18n.sentAttachment();
}
if (message.poll) {
noticontent ||= I18n.sentPoll();
}
if (message.stickers.length > 0) {
noticontent ||= I18n.sentSticker();
}
// exhausted all options, sorry, we just send the raw thing!
noticontent ||= message.content.rawString;
noticontent ||= I18n.blankMessage();
const image = message.getimages()[0];
+3
View File
@@ -36,6 +36,9 @@ class Sticker extends SnowFlake {
getHTML(): HTMLElement {
const img = createImg(
this.owner.info.cdn + "/stickers/" + this.id + ".webp" + new CDNParams({expectedSize: 160}),
undefined,
undefined,
"sticker",
);
img.classList.add("sticker");
const hover = new Hover(this.name);
+123 -72
View File
@@ -7,6 +7,7 @@ body {
color: var(--primary-text);
overflow: hidden; /* avoid "bounce" */
-webkit-text-size-adjust: 100%;
scrollbar-color: var(--button-bg) var(--primary-bg);
}
#page {
height: 100svh;
@@ -274,7 +275,7 @@ body {
flex-direction: column;
}
.pollBody {
background: #00000059;
background: var(--secondary-bg);
padding: 10px;
border-radius: 4px;
margin-top: 2px;
@@ -284,13 +285,12 @@ body {
}
}
.answerArea {
--bg: #0000004a;
--bg: color-mix(in srgb, var(--accent-color) 15%, transparent);
background: var(--bg);
margin-top: 3px;
margin-bottom: 6px;
padding: 12px;
cursor: pointer;
position: relative;
input {
margin-left: auto;
cursor: pointer;
@@ -1058,10 +1058,12 @@ textarea {
right: 5px;
transition: top 0.2s;
transition-timing-function: ease-in;
background: white;
backdrop-filter: blur(20px);
mix-blend-mode: difference;
background: #969696;
cursor: pointer;
&.favorited {
background: yellow;
background: orange;
}
}
.visually-hidden {
@@ -1132,6 +1134,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;
@@ -1304,7 +1310,7 @@ textarea {
position: absolute;
right: 10px;
top: 10px;
background: #0000009e;
background: var(--secondary-bg);
padding: 6px;
border-radius: 100%;
cursor: pointer;
@@ -1598,6 +1604,12 @@ textarea {
display: none;
}
}
#filedroptext {
max-width: 75%;
max-height: 75vh;
padding: 8em 8em 8em 8em;
text-align: center;
}
.commandError {
position: absolute;
top: -36px;
@@ -2076,6 +2088,7 @@ span.instanceStatus {
align-items: start;
padding-top: 10px;
background-size: cover;
color: white;
.ellipsis {
text-shadow: 0px 0px 8px black;
}
@@ -2146,6 +2159,9 @@ span.instanceStatus {
display: flex;
align-items: center;
transition: font-weight 0.1s;
> .ellipsis {
margin-bottom: 0.25em;
}
}
.channelbutton:hover {
background: var(--channel-hover);
@@ -2362,7 +2378,7 @@ span.instanceStatus {
}
#userdock {
padding: 4px 6px;
padding: 11.75px 6px;
background: var(--dock-bg);
align-items: center;
justify-content: space-between;
@@ -2452,7 +2468,7 @@ span.instanceStatus {
flex: 0;
}
#pasteimage {
height: 30%;
max-height: 30%;
padding: 12px;
margin: 16px;
background: var(--typebox-bg);
@@ -2477,13 +2493,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 {
@@ -2491,16 +2508,13 @@ span.instanceStatus {
overflow: clip;
width: fit-content;
height: fit-content;
}
.messageimg {
height: 100%;
width: 100%;
object-fit: contain;
user-select: none;
cursor: pointer;
}
.attachments .messageimg {
border-radius: 4px;
img, div {
height: 100%;
width: 100%;
object-fit: contain;
user-select: none;
cursor: pointer;
}
}
#replybox {
height: 32px;
@@ -2551,6 +2565,7 @@ span.instanceStatus {
flex-shrink: 1;
text-wrap: auto;
overflow-y: auto;
word-break: break-word;
margin-right: 0.03in;
padding: 10px 0;
}
@@ -2866,12 +2881,13 @@ span.instanceStatus {
background: color-mix(in srgb, var(--accent-color) 75%, transparent) !important;
font-size: 0.75em !important;
font-weight: bold;
color: var(--primary-text-prominent) !important;
color: white !important;
border-radius: 4px;
flex: none;
width: fit-content;
background-clip: border-box !important;
align-self: center;
vertical-align: center;
}
.membererror {
display: inline-block;
@@ -3087,11 +3103,11 @@ span .quote:last-of-type .quoteline {
aspect-ratio: 1.9;
}
.mentionMD {
color: transparent;
background-clip: text;
padding: 1px 2px;
border-radius: 4px;
cursor: pointer;
color: color-mix(in srgb, var(--userbg) 75%, var(--primary-text));
background-image: var(--userbg, linear-gradient(var(--primary-text), var(--primary-text)));
position: relative;
z-index: 1;
@@ -3109,6 +3125,24 @@ span .quote:last-of-type .quoteline {
.mentionMD:hover::before {
background: var(--mention);
}
.smallemoji {
height: auto;
width: auto;
max-height: 1.25em;
max-width: 1.25em;
vertical-align: middle;
}
.bigemoji {
width: 0.6in;
object-fit: contain;
height: 0.6in;
vertical-align: middle;
}
.bigemojiUni {
font-size: 47px;
}
.reactiondiv {
padding-left: 52px;
gap: 0 4px;
@@ -3116,7 +3150,8 @@ span .quote:last-of-type .quoteline {
user-select: none;
}
.reaction {
min-width: 30px;
min-width: 1.25em;
min-height: 1.25em;
padding: 3px 4px;
margin-top: 4px;
background: var(--reaction-bg);
@@ -3127,6 +3162,7 @@ span .quote:last-of-type .quoteline {
align-items: center;
cursor: pointer;
gap: 4px;
font-size: 1.25em;
}
.reaction p {
line-height: 1em;
@@ -3138,12 +3174,6 @@ span .quote:last-of-type .quoteline {
background: var(--reaction-reacted-bg);
border-color: color-mix(in srgb, var(--accent-color), transparent);
}
.smallemoji {
height: auto;
width: auto;
max-height: 16px;
max-width: 16px;
}
/* Message External */
.filename a {
@@ -3154,8 +3184,25 @@ 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%;
}
.messageimgdiv {
img, video {
border-radius: 4px;
}
}
.messagegallerydiv {
object-fit: fill;
height: 200px;
max-width: 50%;
overflow: hidden;
img, video {
height: 100%;
object-fit: cover;
}
}
}
.embed {
@@ -3235,6 +3282,8 @@ img.bigembedimg {
margin-top: 8px;
border-radius: 4px;
cursor: pointer;
width: -webkit-fill-available;
width: -moz-available;
}
.inviteEmbed {
width: 400px;
@@ -3302,7 +3351,7 @@ img.bigembedimg {
/* Sidebar */
#sideContainDiv {
padding: 16px 8px;
padding: 16px 8px 0px 8px;
display: none;
flex: none;
width: 240px;
@@ -3372,33 +3421,34 @@ img.bigembedimg {
}
}
::highlight(highkeyword) {
color: yellow;
color: var(--code-keyword);
}
::highlight(highnumber) {
color: orange;
color: var(--code-number);
}
::highlight(highstring) {
color: red;
color: var(--code-string);
}
::highlight(highsymbol) {
color: white;
color: var(--primary-text-prominent);
font-weight: bold;
}
::highlight(highcomment) {
color: green;
color: var(--code-comment);
}
::highlight(highidentifier) {
color: white;
color: var(--primary-text);
}
::highlight(highClass) {
color: #6741ff;
color: var(--code-class);
}
::highlight(highProp) {
color: #33dcff;
color: var(--code-prop);
}
::highlight(highFunc) {
color: #d564ff;
color: var(--code-func);
}
#memberlisttoggle.svgicon {
-moz-appearance: initial;
&::before {
@@ -3489,9 +3539,12 @@ img.bigembedimg {
border-radius: 8px;
overflow: hidden;
align-items: flex-start;
z-index: 3;
z-index: 4;
display: flex;
flex-direction: column;
.banner {
height: 64px;
}
.infosection {
background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent);
}
@@ -4488,32 +4541,34 @@ fieldset input[type="radio"] {
position: relative;
padding: 0px;
cursor: pointer;
flex-direction: row-reverse;
&::after {
content: "";
background: var(--secondary-bg);
transition: left 0.2s;
transition: right 0.2s;
border-radius: 4px;
width: 18px;
height: 18px;
display: block;
top: 3px;
left: 2px;
right: 2px;
position: absolute;
}
:first-child {
:last-child {
border-radius: 5px 0px 0px 5px;
}
:last-child {
:first-child {
border-radius: 0px 5px 5px 0px;
}
&:has(:first-child.selected)::after {
left: 3px;
right: 3px;
}
&:has(:nth-child(2).selected)::after {
left: 27px;
right: 27px;
}
&:has(:nth-child(3).selected)::after {
left: 51px;
right: 51px;
}
}
.tritoggle > .triOpt {
@@ -4854,18 +4909,15 @@ fieldset input[type="radio"] {
.friendcontainer {
display: flex;
width: 100%;
padding: 0.2in;
padding: 0.15in;
overflow-y: auto;
height: 100%;
align-items: stretch;
box-sizing: border-box;
> div {
background: #00000030;
margin-bottom: 0.1in;
padding: 0.06in 0.1in;
border-radius: 0.1in;
border: solid 1px var(--black);
margin: 0 0;
border-bottom: solid 1px var(--secondary-bg);
}
}
.fixedsearch {
@@ -4922,26 +4974,17 @@ fieldset input[type="radio"] {
transition: background-color 0.2s;
padding: 0.08in;
}
.bigemoji {
width: 0.6in;
object-fit: contain;
height: 0.6in;
}
.bigemojiUni {
font-size: 47px;
}
.friendlyButton {
padding: 0.07in;
background: #00000045;
transition: background 0.2s;
border-radius: 1in;
border: solid 1px var(--black);
border: solid 1px var(--secondary-bg);
width: 24px;
height: 24px;
margin: 0 0.05in;
}
.friendlyButton:hover {
background: black;
background: var(--secondary-hover);
}
.stickerOption {
border: solid 1px var(--black);
@@ -4993,14 +5036,15 @@ fieldset input[type="radio"] {
.gifPreviewBox {
position: relative;
width: 2in;
height: 1in;
margin-bottom: 10px;
border-radius: 7px;
overflow: hidden;
cursor: pointer;
img {
width: 2in;
height: 1in;
width: 100%;
height: 100%;
object-fit: cover;
}
span {
@@ -5012,8 +5056,9 @@ fieldset input[type="radio"] {
display: inline-flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--card-bg) 60%, transparent);
font-weight: bold;
color: white;
background: color-mix(in srgb, black 50%, transparent);
}
}
.gifbox {
@@ -5150,7 +5195,6 @@ img.error::after {
border-radius: 2in;
}
.stickerMArea {
padding-left: 48px;
}
.solidBackground {
background: var(--secondary-bg);
@@ -5188,6 +5232,13 @@ img.error::after {
.guildEmojiText {
display: flex;
justify-content: center;
word-break: break-all;
}
.guildDesc {
color: var(--secondary-text-soft);
font-size: 0.9em;
max-height: 15em;
overflow-y: scroll;
}
.optionElement:has(.friendGroupSelect) {
@@ -5259,7 +5310,7 @@ img.error::after {
background: transparent;
width: 48px;
height: 48px;
img {
.pfpDiv {
width: 24px;
height: 24px;
&:nth-child(1) {
+18 -1
View File
@@ -12,6 +12,14 @@
--blue: #779bff;
--grey: #b5b5b5;
--update: var(--green);
--code-keyword: yellow;
--code-number: orange;
--code-string: red;
--code-comment: green;
--code-class: #6741ff;
--code-prop: #33dcff;
--code-func: #d564ff;
}
/* Default theme that looks "just about grey" */
@@ -43,7 +51,7 @@
--primary-button-bg: color-mix(in srgb, #000000 10%, var(--accent-color));
--primary-bg: #fefefe;
--primary-hover: #f6f6f9;
--primary-text: #4b4b59;
--primary-text: black;
--primary-text-soft: #656575;
--secondary-bg: #e0e0ea;
@@ -66,6 +74,15 @@
--secondary-text-soft: #4c4c5a;
--dock-bg: #d1d1df;
--dock-hover: #b8b8d0;
--code-bg: #fdfdf4;
--code-keyword: rgb(180, 180, 76);
--code-number: rgb(161, 109, 12);
--code-string: rgb(153, 4, 4);
--code-comment: rgb(9, 116, 9);
--code-class: #230a86;
--code-prop: #027d96;
--code-func: #6c0091;
}
.Light-theme {
+376
View File
@@ -0,0 +1,376 @@
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 && !TypeBox.inPre()) {
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;
}
static inPre() {
const selection = window.getSelection();
if (!selection) return false;
let node = selection.anchorNode;
while (node) {
if (node instanceof HTMLPreElement) return true;
node = node.parentElement;
}
return false;
}
private static async handleEnter(event: KeyboardEvent): Promise<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 && !this.inPre()) {
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);
}
}
if (TypeBox.box) {
TypeBox.init();
}
@@ -30,6 +30,8 @@ export class UserPreferences {
// render settings
animateIcons: AnimateTristateValue = AnimateTristateValue.OnlyOnHover;
animateGifs: AnimateTristateValue = AnimateTristateValue.OnlyOnHover;
animateEmoji: AnimateTristateValue = AnimateTristateValue.Always;
animateSticker: AnimateTristateValue = AnimateTristateValue.Always;
renderJoinAvatars: boolean = true;
theme: ThemeOption = ThemeOption.Dark;
accentColor: string = "#5865F2";
+17 -88
View File
@@ -290,87 +290,6 @@ export function adduser(user: typeof Specialuser.prototype.json): Specialuser {
localStorage.setItem("userinfos", JSON.stringify(info));
return suser;
}
class Directory {
static home = this.createHome();
handle: FileSystemDirectoryHandle;
writeWorker?: Worker;
private constructor(handle: FileSystemDirectoryHandle) {
this.handle = handle;
}
static async createHome(): Promise<Directory> {
navigator.storage.persist();
const home = new Directory(await navigator.storage.getDirectory());
return home;
}
async *getAllInDir() {
for await (const [name, handle] of this.handle.entries()) {
if (handle instanceof FileSystemDirectoryHandle) {
yield [name, new Directory(handle)] as [string, Directory];
} else if (handle instanceof FileSystemFileHandle) {
yield [name, await handle.getFile()] as [string, File];
} else {
console.log(handle, "oops :3");
}
}
console.log("done");
}
async getRawFileHandler(name: string) {
return await this.handle.getFileHandle(name);
}
async getRawFile(name: string) {
try {
return await (await this.handle.getFileHandle(name)).getFile();
} catch {
return undefined;
}
}
async getString(name: string): Promise<string | undefined> {
try {
return await (await this.getRawFile(name))!.text();
} catch {
return undefined;
}
}
initWorker() {
if (this.writeWorker) return this.writeWorker;
this.writeWorker = new Worker("/utils/dirrWorker.js");
this.writeWorker.onmessage = (event) => {
const res = this.wMap.get(event.data[0]);
this.wMap.delete(event.data[0]);
if (!res) throw new Error("Res is not defined here somehow");
res(event.data[1]);
};
return this.writeWorker;
}
wMap = new Map<number, (input: boolean) => void>();
async setStringWorker(name: FileSystemFileHandle, value: ArrayBuffer) {
const worker = this.initWorker();
const random = Math.random();
worker.postMessage([name, value, random]);
return new Promise<boolean>((res) => {
this.wMap.set(random, res);
});
}
async setString(name: string, value: string): Promise<boolean> {
const file = await this.handle.getFileHandle(name, {create: true});
const contents = new TextEncoder().encode(value);
if (file.createWritable as unknown) {
const stream = await file.createWritable({keepExistingData: false});
await stream.write(contents);
await stream.close();
return true;
} else {
//Curse you webkit!
return await this.setStringWorker(file, contents.buffer as ArrayBuffer);
}
}
async getDir(name: string) {
return new Directory(await this.handle.getDirectoryHandle(name, {create: true}));
}
}
export {Directory};
const mobile =
/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
@@ -743,18 +662,28 @@ export type safeImg = HTMLImageElement & {
setSrcs: (nsrc: string, nstaticsrc: string | void) => void;
isAnimated: () => Promise<boolean>;
};
function getSetting(type: "gif" | "icon" | "emoji" | "sticker") {
const prefs = getPreferences();
switch (type) {
case "gif":
return prefs.animateGifs;
case "icon":
return prefs.animateIcons;
case "emoji":
return prefs.animateEmoji;
case "sticker":
return prefs.animateSticker;
default:
return "hover";
}
}
export function createImg(
src: string | undefined,
staticsrc: string | void,
elm: HTMLElement | void,
type: "gif" | "icon" = "gif",
type: "gif" | "icon" | "emoji" | "sticker" = "gif",
): safeImg {
const prefs = getPreferences();
const aniOpt =
(type === "gif" ? prefs.animateGifs : prefs.animateIcons) ||
("hover" as "hover") ||
"always" ||
"never";
const aniOpt = getSetting(type);
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";