mirror of
https://github.com/MathMan05/Fermi.git
synced 2026-09-09 13:56:14 +00:00
Merge branch 'main' into pr-451
This commit is contained in:
+44
-109
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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("#.*");
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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")!,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user