Merge branch 'main' into pr-412

This commit is contained in:
Mathium05
2026-08-02 16:57:25 -05:00
30 changed files with 764 additions and 598 deletions
+71 -143
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;
@@ -44,6 +44,10 @@ class Channel extends SnowFlake {
owner: Guild;
headers: Localuser["headers"];
name!: string;
get shortName() {
if (this.name.length > 50) return this.name.slice(0, 50) + "...";
return this.name;
}
parentId?: string;
parent?: Channel;
children!: Channel[];
@@ -269,6 +273,17 @@ class Channel extends SnowFlake {
);
this.contextmenu.addSeperator();
this.contextmenu.addButton(
I18n.channel.copyURL(),
function (this: Channel) {
navigator.clipboard.writeText(`${location.origin}/channels/${this.guild.id}/${this.id}`);
},
{
visible: function () {
return this.type !== 4;
},
},
);
//TODO copy ID icon
this.contextmenu.addButton(
function () {
@@ -399,7 +414,7 @@ class Channel extends SnowFlake {
update();
const inviteOptions = new Dialog("", {noSubmit: true});
inviteOptions.options.addTitle(I18n.inviteOptions.title());
inviteOptions.options.addText(I18n.invite.subtext(this.name, this.guild.properties.name));
inviteOptions.options.addText(I18n.invite.subtext(this.shortName, this.guild.properties.name));
inviteOptions.options.addSelect(
I18n.invite.expireAfter(),
@@ -427,7 +442,7 @@ class Channel extends SnowFlake {
inviteOptions.show();
}
generateSettings() {
const settings = new Settings(I18n.channel.settingsFor(this.name));
const settings = new Settings(I18n.channel.settingsFor(this.shortName));
{
const gensettings = settings.addButton(I18n.channel.settings());
const form = gensettings.addForm("", () => {}, {
@@ -1052,7 +1067,7 @@ class Channel extends SnowFlake {
const myhtml = document.createElement("p2");
myhtml.classList.add("ellipsis");
myhtml.textContent = this.name;
myhtml.textContent = this.shortName;
this.nameSpan = new WeakRef(myhtml);
decdiv.appendChild(myhtml);
caps.appendChild(decdiv);
@@ -1119,7 +1134,7 @@ class Channel extends SnowFlake {
div.append(button);
const myhtml = document.createElement("span");
myhtml.classList.add("ellipsis");
myhtml.textContent = this.name;
myhtml.textContent = this.shortName;
this.nameSpan = new WeakRef(myhtml);
const decoration = this.renderIcon();
button.appendChild(decoration);
@@ -1422,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);
@@ -1528,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) {
@@ -1938,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;
@@ -2017,7 +1988,7 @@ class Channel extends SnowFlake {
div.append(tags);
const title = document.createElement("h3");
title.textContent = new MarkDown(this.name).makeHTML().textContent;
title.textContent = new MarkDown(this.shortName).makeHTML().textContent;
div.append(title);
const member =
@@ -2609,25 +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();
}
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
typebox.markdown.boxEnabled = !this.curCommand;
if (this.curCommand) {
this.curCommand.render(typebox, this);
}
typebox.style.setProperty("--channel-text", JSON.stringify(I18n.channel.typebox(this.name)));
TypeBox.saveBox();
if (!this.curCommand && !this.isForum()) {
const md = typebox.markdown;
md.owner = this;
typebox.textContent = this.textSave;
md.boxupdate(Infinity);
TypeBox.restoreBox(this);
}
if (this.isForum()) {
typebox.textContent = "";
TypeBox.markdown.boxEnabled = !this.curCommand;
if (this.curCommand) {
this.curCommand.render(TypeBox.box, this);
}
this.localuser.fileExtange(this.files, this.htmls);
TypeBox.box.style.setProperty(
"--channel-text",
JSON.stringify(I18n.channel.typebox(this.shortName)),
);
if (getMessages === undefined) {
getMessages = this.type !== 2 || !this.localuser.voiceAllowed;
@@ -2670,7 +2634,7 @@ class Channel extends SnowFlake {
"/channels/" + this.guild.id + "/" + this.id + (aroundMessage ? `/${aroundMessage}` : ""),
);
}
this.localuser.pageTitle("#" + this.name);
this.localuser.pageTitle("#" + this.shortName);
const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement;
if (this.topic) {
channelTopic.innerHTML = "";
@@ -2743,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) {
@@ -2920,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;
}
@@ -3236,42 +3189,17 @@ class Channel extends SnowFlake {
this.parentId = undefined;
}
const oldover = this.permissionOverwriteMap;
this.permissionOverwriteMap.clear();
(json.permission_overwrites ?? []).forEach((r) => {
const p = new Permissions(r.allow, r.deny);
this.permissionOverwriteMap.set(r.id, p);
});
const nchange = new Set<string>(oldover.keys()).difference(this.permissionOverwriteMap);
const pchange = new Set<string>(this.permissionOverwriteMap.keys()).difference(oldover);
for (const thing of nchange) {
const role = this.guild.roleids.get(thing);
if (role) {
this.croleUpdate(role, new Permissions("0"), false);
} else {
const user = this.localuser.getUser(thing);
user.then((_) => {
if (_) this.croleUpdate(_, new Permissions("0"), false);
});
}
}
for (const thing of pchange) {
const role = this.guild.roleids.get(thing);
const perms = this.permissionOverwriteMap.get(thing);
if (role && perms) {
this.croleUpdate(role, perms, true);
} else if (perms) {
const user = this.localuser.getUser(thing);
user.then((_) => {
if (_) this.croleUpdate(_, perms, true);
});
}
}
this.croleUpdate();
this.topic = json.topic;
this.nsfw = json.nsfw;
this.fireEvents();
}
croleUpdate: (role: Role | User, perm: Permissions, added: boolean) => unknown = () => {};
croleUpdate: () => unknown = () => {};
typingstart() {
if (this.typing > Date.now()) {
return;
+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());
+15 -10
View File
@@ -158,6 +158,7 @@ class File {
video.append(source);
//source.type = this.content_type;
video.controls = !temp;
video.preload = "metadata";
if (this.width) video.width = this.width;
if (this.height) video.height = this.height;
@@ -276,19 +277,20 @@ class File {
createunknown(url: Promise<string> | void): HTMLElement {
console.log("🗎");
const src = this.proxy_url || this.url;
const div = document.createElement("table");
div.classList.add("unknownfile");
const nametr = document.createElement("tr");
div.append(nametr);
const div = document.createElement("div");
div.classList.add("unknownfile", "flexltr");
const fileicon = document.createElement("td");
nametr.append(fileicon);
div.append(fileicon);
fileicon.append("🗎");
fileicon.classList.add("fileicon");
fileicon.rowSpan = 2;
const nametd = document.createElement("td");
const nametd = document.createElement("div");
nametd.classList.add("flexttb");
if (src) {
const a = document.createElement("a");
a.href = src;
a.target = "_blank";
a.rel = "noopener noreferrer";
if (url)
url.then((_) => {
a.href = _;
@@ -300,7 +302,7 @@ class File {
}
nametd.classList.add("filename");
nametr.append(nametd);
div.append(nametd);
const sizetr = document.createElement("tr");
const size = document.createElement("td");
sizetr.append(size);
@@ -310,11 +312,14 @@ class File {
return div;
}
static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
// These DO change between languages, for example in russian it uses cyrillic script
// also NOBODY is uploading TBs of files... seriously no
// And finally we are using SI units, so we go in thousands :)
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1000));
return (
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
Number((fsize / Math.pow(1000, i)).toFixed(2)) * 1 +
" " +
["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong
[I18n.filesize.B(), I18n.filesize.KB(), I18n.filesize.MB(), I18n.filesize.GB()][i]
);
}
}
+26 -30
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, {
@@ -554,6 +555,14 @@ class Guild extends SnowFlake {
dio.show();
}
regenRLPerms() {
this.sortRoles();
const permlist: [Role, Permissions][] = [];
for (const thing of this.roles) {
permlist.push([thing, thing.permissions]);
}
return permlist;
}
generateSettings() {
const settings = new Settings(I18n.guild.settingsFor(this.properties.name));
const textChannels = this.channels.filter((e) => {
@@ -697,10 +706,7 @@ class Guild extends SnowFlake {
this.makeInviteMenu(settings.addButton(I18n.invite.inviteMaker()), textChannels);
if (this.member.hasPermission("MANAGE_ROLES")) {
const s1 = settings.addButton(I18n.guild.roles(), {optName: ""});
const permlist: [Role, Permissions][] = [];
for (const thing of this.roles) {
permlist.push([thing, thing.permissions]);
}
const permlist = this.regenRLPerms();
s1.options.push(new RoleList(permlist, this, this.updateRolePermissions.bind(this), false));
}
if (this.member.hasPermission("MANAGE_GUILD_EXPRESSIONS")) {
@@ -1474,17 +1480,20 @@ class Guild extends SnowFlake {
this.emojis = json.emojis || [];
this.headers = this.owner.headers;
this.welcomeScreen = json.welcome_screen;
this.properties.features = json.features;
if (this.properties.icon !== json.icon) {
this.properties.icon = json.icon;
if (this.HTMLicon) {
const divy = this.generateGuildIcon();
this.HTMLicon.replaceWith(divy);
this.HTMLicon = divy;
if (this.properties)
if (this.properties.icon !== json.icon) {
this.properties.icon = json.icon;
if (this.HTMLicon) {
const divy = this.generateGuildIcon();
this.HTMLicon.replaceWith(divy);
this.HTMLicon = divy;
}
}
}
this.roleids.clear();
this.banner = json.banner;
this.welcomeScreen = json.welcome_screen;
this.properties = json;
}
constructor(json: guildjson | -1, owner: Localuser, member: memberjson | User | null) {
super(typeof json === "number" ? "@me" : json.id);
@@ -1495,20 +1504,14 @@ class Guild extends SnowFlake {
if (json === -1 || member === null) {
return;
}
if (json.stickers.length) {
console.log(json.stickers, ":3");
}
this.large = json.large;
this.update({...json.properties, large: json.large, emojis: json.emojis});
this.member_count = json.member_count;
this.emojis = json.emojis || [];
this.channels = [];
if (json.properties) {
this.properties = json.properties;
}
this.roles = [];
this.banner = json.properties.banner;
this.welcomeScreen = json.properties.welcome_screen;
if (json.roles) {
for (const roley of json.roles) {
const roleh = new Role(roley, this);
@@ -1953,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) {
+37 -4
View File
@@ -1,6 +1,6 @@
[
{
"names":["js","ts","javascript"],
"names":["js","ts","typescript","javascript","tscript","jscript"],
"keywords": ["break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do","else", "export", "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield", "let", "await", "true", "false", "null", "undefined"],
"firstLineShebang": true,
"doubleSlashComments": true,
@@ -8,14 +8,14 @@
"JSLikeTemplateStrings": true
},
{
"names":["h","c","hc"],
"names":["h","c","hc","c23","c89","ansic","ansi-c","khr-c","khrc"],
"keywords":["auto", "break", "case", "char", "continue", "do", "default", "const", "double", "else", "enum", "extern", "for", "if", "goto", "float", "int", "long", "register", "return", "signed", "static", "sizeof", "short", "struct", "switch", "typedef", "union", "void", "while", "volatile", "unsigned"],
"hashComments": true,
"doubleSlashComments": true,
"multilineSlashComments": true
},
{
"names":["c++","cpp","hpp","cxx","hxx","h++"],
"names":["cplusplus","hplusplus","c++","cpp","hpp","cxx","hxx","h++"],
"keywords":["alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "contract_assert", "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq", "final", "override", "transaction_safe", "transaction_safe_dynamic", "import", "module", "pre", "post"],
"hashComments": true,
"doubleSlashComments": true,
@@ -52,5 +52,38 @@
"keywords": ["False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield"],
"hashComments": true,
"multiLine":"\"\"\""
}
},
{
"names": ["java", "jav", "jv"],
"keywords": ["abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while"],
"firstLineShebang":true,
"doubleSlashComments": true,
"multilineSlashComments": true,
"multiLine":"\"\"\""
},
{
"names": ["glsl","glslang","opengl","glslc"],
"keywords": ["const", "uniform", "buffer", "shared", "attribute", "varying", "coherent", "volatile", "restrict", "readonly", "writeonly", "atomic_uint", "layout", "centroid", "flat", "smooth", "noperspective", "patch", "sample", "invariant", "precise", "break", "continue", "do", "for", "while", "switch", "case", "default", "if", "else", "subroutine", "in", "out", "inout", "int", "void", "bool", "true", "false", "float", "double", "discard", "return", "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", "uint", "uvec2", "uvec3", "uvec4", "dvec2", "dvec3", "dvec4", "mat2", "mat3", "mat4", "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4", "dmat2", "dmat3", "dmat4", "dmat2x2", "dmat2x3", "dmat2x4", "dmat3x2", "dmat3x3", "dmat3x4", "dmat4x2", "dmat4x3", "dmat4x4", "lowp", "mediump", "highp", "precision", "sampler1D", "sampler1DShadow", "sampler1DArray", "sampler1DArrayShadow", "isampler1D", "isampler1DArray", "usampler1D", "usampler1DArray", "sampler2D", "sampler2DShadow", "sampler2DArray", "sampler2DArrayShadow", "isampler2D", "isampler2DArray", "usampler2D", "usampler2DArray", "sampler2DRect", "sampler2DRectShadow", "isampler2DRect", "usampler2DRect", "sampler2DMS", "isampler2DMS", "usampler2DMS", "sampler2DMSArray", "isampler2DMSArray", "usampler2DMSArray", "sampler3D", "isampler3D", "usampler3D", "samplerCube", "samplerCubeShadow", "isamplerCube", "usamplerCube", "samplerCubeArray", "samplerCubeArrayShadow", "isamplerCubeArray", "usamplerCubeArray", "samplerBuffer", "isamplerBuffer", "usamplerBuffer", "image1D", "iimage1D", "uimage1D", "image1DArray", "iimage1DArray", "uimage1DArray", "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2DRect", "iimage2DRect", "uimage2DRect", "image2DMS", "iimage2DMS", "uimage2DMS", "image2DMSArray", "iimage2DMSArray", "uimage2DMSArray", "image3D", "iimage3D", "uimage3D", "imageCube", "iimageCube", "uimageCube", "imageCubeArray", "iimageCubeArray", "uimageCubeArray", "imageBuffer", "iimageBuffer", "uimageBuffer", "struct", "In", "addition,", "when", "targeting", "Vulkan,", "the", "following", "keywords", "also", "exist:", "texture1D", "texture1DArray", "itexture1D", "itexture1DArray", "utexture1D", "utexture1DArray", "texture2D", "texture2DArray", "itexture2D", "itexture2DArray", "utexture2D", "utexture2DArray", "texture2DRect", "itexture2DRect", "utexture2DRect", "texture2DMS", "itexture2DMS", "utexture2DMS", "texture2DMSArray", "itexture2DMSArray", "utexture2DMSArray", "texture3D", "itexture3D", "utexture3D", "textureCube", "itextureCube", "utextureCube", "textureCubeArray", "itextureCubeArray", "utextureCubeArray", "textureBuffer", "itextureBuffer", "utextureBuffer", "sampler", "samplerShadow", "subpassInput", "isubpassInput", "usubpassInput", "subpassInputMS", "isubpassInputMS", "usubpassInputMS", "The", "following", "are", "the", "keywords", "reserved", "for", "future", "use.", "Using", "them", "will", "result", "in", "a", "compile-time", "error:", "common", "partition", "active", "asm", "class", "union", "enum", "typedef", "template", "this", "resource", "goto", "inline", "noinline", "public", "static", "extern", "external", "interface", "long", "short", "half", "fixed", "unsigned", "superp", "input", "output", "hvec2", "hvec3", "hvec4", "fvec2", "fvec3", "fvec4", "filter", "sizeof", "cast", "namespace", "using", "sampler3DRect"],
"hashComments": true,
"doubleSlashComments": true,
"multilineSlashComments": true
},
{
"names": ["hlsl","hlslang","hlslc","directx"],
"keywords": [
"AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream",
"uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", "VertexShader", "void", "volatile", "while", "uint", "uint1", "uint2", "uint3", "uint4", "uint1x1", "uint1x2", "uint1x3", "uint1x4 uint2x1", "uint2x2", "uint2x3", "uint2x4 uint3x1", "uint3x2", "uint3x3", "uint3x4 uint4x1", "uint4x2", "uint4x3", "uint4x4", "bool", "bool1", "bool2", "bool3", "bool4", "bool1x1", "bool1x2", "bool1x3", "bool1x4 bool2x1", "bool2x2", "bool2x3", "bool2x4 bool3x1", "bool3x2", "bool3x3", "bool3x4 bool4x1", "bool4x2", "bool4x3", "bool4x4", "int", "int1", "int2", "int3", "int4", "int1x1", "int1x2", "int1x3", "int1x4 int2x1", "int2x2", "int2x3", "int2x4 int3x1", "int3x2", "int3x3", "int3x4 int4x1", "int4x2", "int4x3", "int4x4", "float", "float1", "float2", "float3", "float4", "float1x1", "float1x2", "float1x3", "float1x4 float2x1", "float2x2", "float2x3", "float2x4 float3x1", "float3x2", "float3x3", "float3x4 float4x1", "float4x2", "float4x3", "float4x4"
],
"hashComments": true,
"doubleSlashComments": true,
"multilineSlashComments": true
},
{
"names": ["kt","kot","kotlin","kts"],
"keywords": ["as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while", "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", "param", "property", "receiver", "set", "setparam", "value", "where", "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg"],
"firstLineShebang":true,
"doubleSlashComments": true,
"multilineSlashComments": true,
"multiLine":"\"\"\""
}
]
+1 -1
View File
@@ -49,7 +49,7 @@ class Hover {
this.elm2 = await this.makeHover(elm);
Hover.bound = elm;
Hover.watchForGone();
}, 300);
}, 100);
});
elm.addEventListener("mouseout", () => {
clearTimeout(timeOut);
+6 -7
View File
@@ -119,13 +119,12 @@ class I18n {
static options() {
return [...langmap.keys()].map((e) => e.replace(".json", ""));
}
static setLanguage(lang: string) {
static async setLanguage(lang: string) {
if (this.options().indexOf(lang) !== -1) {
getPreferences().then(async (prefs) => {
prefs.locale = lang;
await I18n.create(lang);
await setPreferences(prefs);
});
const prefs = getPreferences();
prefs.locale = lang;
await I18n.create(lang);
await setPreferences(prefs);
}
}
}
@@ -134,7 +133,7 @@ let userLocale = navigator.language.slice(0, 2) || "en";
if (I18n.options().indexOf(userLocale) === -1) {
userLocale = "en";
}
const prefs = await getPreferences();
const prefs = getPreferences();
const storage = prefs.locale;
if (storage) {
userLocale = storage;
+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

+6 -262
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());
@@ -464,30 +227,11 @@ if (window.location.pathname.startsWith("/channels")) {
},
{
//TODO re-enable this once polls is merged
visible: () => false, //!!thisUser.channelfocus?.hasPermission("SEND_POLLS"),
visible: () => !!thisUser?.focusChannel?.hasPermission("SEND_POLLS"),
},
);
umenu.addButton(I18n.upload(), () => {
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")!,
+45 -45
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;
@@ -337,7 +336,7 @@ class Localuser {
}
async queryBlog() {
this.perminfo.localuser ??= {};
const prefs = await getPreferences();
const prefs = getPreferences();
const bstate = prefs.showBlogUpdates;
if (bstate === undefined) {
const pop = new Dialog("");
@@ -461,7 +460,7 @@ class Localuser {
}
this.pingEndpoint();
const prefs = await getPreferences();
const prefs = getPreferences();
const ml = document.getElementById("memberlisttoggle")!;
if (prefs.checkMemberList) {
ml.classList = "";
@@ -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) {
@@ -1785,7 +1785,7 @@ class Localuser {
//https://cdn.discordapp.com/banners/677271830838640680/fab8570de5bb51365ba8f36d7d3627ae.webp?size=240
banner.style.setProperty(
"background-image",
`linear-gradient(rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 40%), url(${this.info.cdn}/banners/${guild.id}/${guild.banner + new CDNParams({expectedSize: 128})})`,
`linear-gradient(rgba(80, 80, 80, 1) 0%, rgba(80, 80, 80, 0) 40%), url(${this.info.cdn}/banners/${guild.id}/${guild.banner + new CDNParams({expectedSize: 128})})`,
);
banner.classList.add("Banner");
//background-image:
@@ -2058,8 +2058,11 @@ class Localuser {
menu.bindContextmenu(iconDiv);
if (folder.color !== null && folder.color !== undefined) {
icon.style.setProperty("--folder-color", "#" + folder.color.toString(16).padStart(6, "0"));
if (!folder.color && folder.color !== 0) icon.style.removeProperty("--folder-color");
folderDiv.style.setProperty(
"--folder-color",
"#" + folder.color.toString(16).padStart(6, "0"),
);
if (!folder.color && folder.color !== 0) folderDiv.style.removeProperty("--folder-color");
}
iconDiv.append(icon);
const divy = document.createElement("div");
@@ -2483,7 +2486,6 @@ class Localuser {
async getPosts() {
const text = await (await fetch("https://blog.fermi.chat/feed_rss_created.xml")).text();
const xml = new DOMParser().parseFromString(text, "text/xml");
console.log(xml, text);
const posts = Array.from(xml.getElementsByTagName("channel")[0].getElementsByTagName("item"));
return {
items: posts.map((post) => {
@@ -2531,7 +2533,7 @@ class Localuser {
this.figureDefaultProvidor();
}
async figureDefaultProvidor() {
const prefs = await getPreferences();
const prefs = getPreferences();
this.selectedGifProfidor =
this.gifProvideors.find((_) => _.api_name == prefs.gifProvidor) || this.gifProvideors[0];
}
@@ -2583,7 +2585,7 @@ class Localuser {
});
}
async showusersettings() {
const prefs = await getPreferences();
const prefs = getPreferences();
const localSettings = getLocalSettings();
const settings = new Settings(I18n.localuser.settings());
{
@@ -2778,21 +2780,20 @@ class Localuser {
connectionContainer.appendChild(container);
});
//TODO enable this once domain verification is ready within Harmony
if (false as true) {
const container = document.createElement("div");
const span = document.createElement("span");
span.classList.add("conImg", "svgicon");
span.style.setProperty("mask", `url("/icons/domain.svg")`);
container.append(span);
const container = document.createElement("div");
container.addEventListener("click", async () => {
this.domainVerification();
});
const span = document.createElement("span");
span.classList.add("conImg", "svgicon");
span.style.setProperty("mask", `url("/icons/domain.svg")`);
container.append(span);
container.addEventListener("click", async () => {
this.domainVerification();
});
connectionContainer.appendChild(container);
connectionContainer.appendChild(container);
}
serverConnections
.filter((_) => actConMap.has(_))
.forEach((_) => {
@@ -3014,7 +3015,7 @@ class Localuser {
}
{
const prefs = await getPreferences();
const prefs = getPreferences();
const tas = settings.addButton(I18n.localuser.themesAndSounds(), {contained: true});
{
const themes = ["Dark", "WHITE", "Light", "Dark-Accent"];
@@ -3145,6 +3146,18 @@ class Localuser {
{defaultIndex: ind === -1 ? 0 : ind},
);
}
{
tas.addCheckboxInput(
"Show today at:",
(b) => {
prefs.showToday = b;
setPreferences(prefs);
},
{
initState: prefs.showToday,
},
);
}
}
{
const blog = settings.addButton(I18n.blog.blog(), {contained: true});
@@ -4267,21 +4280,12 @@ class Localuser {
});
}
updateSend() {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
if (
(typebox.markdown.rawString && typebox.markdown.rawString !== "\n") ||
document.getElementById("pasteimage")?.children.length
) {
typebox.parentElement!.classList.remove("noConent");
} else {
typebox.parentElement!.classList.add("noConent");
}
TypeBox.updateSend();
}
//TODO make this an option
readonly autofillregex = Object.freeze(/(^|\s|\n)[@#:]([a-zA-Z0-9]*)$/i);
mdBox() {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const typeMd = typebox.markdown;
const typeMd = TypeBox.markdown;
typeMd.owner = this;
typeMd.onUpdate = (str, pre) => {
this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre);
@@ -4308,6 +4312,7 @@ class Localuser {
replyingto: this.focusChannel.replyingto,
});
this.focusChannel.replyingto = null;
this.focusChannel.makereplybox();
}
}
@@ -4515,8 +4520,7 @@ class Localuser {
search.focus();
}
async TBEmojiMenu(rect: DOMRect) {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const p = saveCaretPosition(typebox);
const p = TypeBox.saveCarrot();
if (!p) return;
const original = MarkDown.getText();
@@ -4527,7 +4531,7 @@ class Localuser {
);
this.favorites.addEmoji(emoji.id || (emoji.emoji as string));
p();
const md = typebox.markdown;
const md = TypeBox.markdown;
this.MDReplace(
emoji.id
? `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`
@@ -4562,10 +4566,6 @@ class Localuser {
);
}
}
fileExtange!: (
files: Blob[],
html: WeakMap<Blob, HTMLElement>,
) => [Blob[], WeakMap<Blob, HTMLElement>];
MDSearchOptions(
options: MDSearchOption[],
original: string,
@@ -4685,7 +4685,7 @@ class Localuser {
maybe.sort((a, b) => b[0] - a[0]);
this.MDSearchOptions(
maybe.map((a) => {
return {name: "# " + a[1].name, replace: `<#${a[1].id}> `};
return {name: "# " + a[1].shortName, replace: `<#${a[1].id}> `};
}),
original,
box,
@@ -5236,7 +5236,7 @@ class Localuser {
readonly presences: Map<string, presencejson> = new Map();
static font?: FontFace;
static async loadFont() {
const prefs = await getPreferences();
const prefs = getPreferences();
const fontName = prefs.emojiFont;
if (this.font) {
+8 -4
View File
@@ -27,6 +27,7 @@ import {Components} from "./interactions/compontents.js";
import {ImagesDisplay} from "./disimg";
import {ReportMenu} from "./reporting/report.js";
import {getDeveloperSettings} from "./utils/storage/devSettings.js";
import {getPreferences} from "./utils/storage/userPreferences.js";
class Message extends SnowFlake {
static contextmenu = new Contextmenu<Message, void>("message menu");
stickers!: Sticker[];
@@ -192,7 +193,9 @@ class Message extends SnowFlake {
);
},
{
//TODO make icon
icon: {
css: "svg-link",
},
},
);
Message.contextmenu.addButton(
@@ -1320,7 +1323,7 @@ class Message extends SnowFlake {
question.textContent = this.poll.question.text;
pollbody.append(question);
let ccount = [...r.values()].reduce((e, l) => e + +l.me_voted, 0);
if (this.poll.allow_multiselect) voted = !!ccount;
voted = !!ccount;
for (const a of this.poll.answers) {
const aarea = document.createElement("div");
aarea.classList.add("flexltr", "answerArea");
@@ -1360,7 +1363,7 @@ class Message extends SnowFlake {
count.textContent = I18n.poll.count("" + c, per + "");
aarea.append(count);
if (per)
aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${100 - per}%)`;
aarea.style.background = `linear-gradient(to right, var(--green) ${per}%, var(--bg) ${per}%)`;
}
aarea.onclick = () => check.click();
pollbody.append(aarea);
@@ -1773,13 +1776,14 @@ let now: string;
let yesterdayStr: string;
function formatTime(date: Date) {
const conf = getPreferences();
updateTimes();
const datestring = date.toLocaleDateString();
const formatTime = (date: Date) =>
date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
if (datestring === now) {
return I18n.todayAt(formatTime(date));
return conf.showToday ? I18n.todayAt(formatTime(date)) : formatTime(date);
} else if (datestring === yesterdayStr) {
return I18n.yesterdayAt(formatTime(date));
} else {
+1 -1
View File
@@ -29,7 +29,7 @@ type botjsonfetch = {
guild_id: null | string;
bot_public: boolean;
bot_require_code_grant: boolean;
verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P
verify_key: string; //application client secret
flags: number;
};
bot: {
+3
View File
@@ -36,6 +36,7 @@ class Permissions {
//private static info: { name: string; readableName: string; description: string }[];
static *info(): Generator<{name: string; readableName: string; description: string}> {
for (const thing of this.permisions) {
if (!thing) continue;
yield {
name: thing,
readableName: I18n.permissions.readableNames[thing](),
@@ -91,6 +92,8 @@ class Permissions {
"CREATE_EVENTS",
"USE_EXTERNAL_SOUNDS",
"SEND_VOICE_MESSAGES",
null, //TODO unused
"SET_VOICE_CHANNEL_STATUS",
"SEND_POLLS",
"USE_EXTERNAL_APPS",
"PIN_MESSAGES",
+6 -24
View File
@@ -283,28 +283,15 @@ class RoleList extends Buttons {
});
}
this.options = options;
guild.roleUpdate = this.groleUpdate.bind(this);
guild.roleUpdate = this.roleUpdate.bind(this);
if (channel) {
channel.croleUpdate = this.croleUpdate.bind(this);
channel.croleUpdate = this.roleUpdate.bind(this);
}
}
private groleUpdate(role: Role, added: 1 | 0 | -1) {
if (!this.channel) {
if (added === 1) {
this.permissions.push([role, role.permissions]);
}
}
if (added === -1) {
this.permissions = this.permissions.filter((r) => r[0] !== role);
}
this.redoButtons();
}
private croleUpdate(role: Role | User, perm: Permissions, added: boolean) {
if (added) {
this.permissions.push([role, perm]);
} else {
this.permissions = this.permissions.filter((r) => r[0] !== role);
}
private async roleUpdate() {
this.permissions = this.channel
? await this.channel.getOverwritesOrder()
: this.guild.regenRLPerms();
this.redoButtons();
}
makeguildmenus(option: Options) {
@@ -526,11 +513,6 @@ class RoleList extends Buttons {
}
redoButtons() {
this.buttons = [];
this.permissions.sort(([a], [b]) => {
if (b instanceof User || !(b instanceof Channel)) return 1;
if (a instanceof User || !(a instanceof Channel)) return -1;
return b.position - a.position;
});
for (const i of this.permissions) {
this.buttons.push({
name: "name" in i[0] ? i[0].name : I18n.userping.unknown(),
+42 -25
View File
@@ -19,19 +19,6 @@ body {
background: color-mix(in srgb, var(--green), 20% white);
}
}
.mutFriends {
display: flex;
flex-direction: column;
align-items: stretch;
.createdWebhook {
flex-grow: 1;
background: var(--primary-bg);
margin-bottom: 6px;
width: 100%;
box-sizing: border-box;
}
}
.createdWebhook {
display: flex;
align-items: center;
@@ -1146,6 +1133,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;
@@ -1612,6 +1603,12 @@ textarea {
display: none;
}
}
#filedroptext {
max-width: 75%;
max-height: 75vh;
padding: 8em 8em 8em 8em;
text-align: center;
}
.commandError {
position: absolute;
top: -36px;
@@ -1954,14 +1951,13 @@ span.instanceStatus {
display: flex;
justify-content: center;
align-items: center;
background: var(--folder-bg);
border-radius: 16px;
margin: 2px;
margin-bottom: 8px;
cursor: pointer;
}
.folder-div {
background: color-mix(in srgb, var(--primary-text-soft), 70% transparent);
background: color-mix(in srgb, var(--folder-color, var(--accent-color)), 70% transparent);
padding: 3px;
margin: -3px;
border-radius: 16px;
@@ -2087,10 +2083,13 @@ span.instanceStatus {
user-select: none;
}
.Banner {
height: 100px;
height: 8em;
align-items: start;
padding-top: 10px;
background-size: cover;
.ellipsis {
text-shadow: 0px 0px 8px black;
}
}
#serverName {
font-size: 1rem;
@@ -2464,7 +2463,7 @@ span.instanceStatus {
flex: 0;
}
#pasteimage {
height: 30%;
max-height: 30%;
padding: 12px;
margin: 16px;
background: var(--typebox-bg);
@@ -2489,6 +2488,15 @@ 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;
}
}
.messageimgdiv {
position: relative;
@@ -2555,6 +2563,7 @@ span.instanceStatus {
flex-shrink: 1;
text-wrap: auto;
overflow-y: auto;
word-break: break-word;
margin-right: 0.03in;
padding: 10px 0;
}
@@ -2564,6 +2573,9 @@ span.instanceStatus {
opacity: 0.5;
position: absolute;
cursor: text;
text-wrap: nowrap;
width: 100%;
overflow: hidden;
}
.outerTypeBox {
max-height: 50svh;
@@ -3155,8 +3167,10 @@ span .quote:last-of-type .quoteline {
gap: 4px;
flex-wrap: wrap;
video {
max-height: 288px;
max-width: 288px;
min-height: 200px;
min-width: 200px;
max-height: 25%;
max-width: 50%;
}
}
.embed {
@@ -3485,15 +3499,17 @@ img.bigembedimg {
width: 300px;
max-height: 100svh;
background: var(--card-bg);
box-shadow:
0 0 8px var(--shadow),
inset 0 132px 64px var(--accent_color);
box-shadow: inset 0 0 300px 350px var(--accent_color);
opacity: 1;
border-radius: 8px;
overflow: hidden;
align-items: flex-start;
z-index: 3;
display: flex;
flex-direction: column;
.infosection {
background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent);
}
&:not(.hypoprofile) {
animation-duration: 0.2s;
animation-name: fade-in;
@@ -3506,7 +3522,7 @@ img.bigembedimg {
margin-bottom: 10px;
box-sizing: border-box;
padding: 10px;
background: var(--secondary-bg);
background-color: color-mix(in srgb, var(--secondary-bg) 75%, transparent);
border: none;
border-radius: 5px;
font-size: 14px;
@@ -4338,12 +4354,12 @@ fieldset input[type="radio"] {
.traceBars {
padding: 10px 0px;
}
.mutGuildBox {
.mutGuildBox,
.mutFriends {
background: var(--primary-bg);
padding: 6px;
cursor: pointer;
margin-top: 6px;
img {
width: 48px !important;
height: 48px !important;
@@ -4451,6 +4467,7 @@ fieldset input[type="radio"] {
}
.conProfDiv {
margin: 6px;
align-items: center;
}
.disabled .conImg {
+1 -1
View File
@@ -24,7 +24,7 @@
--primary-text-soft: #adb8b9;
--secondary-bg: #16191b;
--secondary-hover: #252b2c;
--servers-bg: #191c1d;
--servers-bg: #151718;
--channels-bg: #2a2d33;
--channel-selected: #3c4046;
--typebox-bg: #3a3e45;
+363
View File
@@ -0,0 +1,363 @@
import {Channel} from "./channel";
import {Localuser} from "./localuser";
import {MarkDown, saveCaretPosition} from "./markdown";
import {File} from "./file";
import {I18n} from "./i18n";
import {mobile} from "./utils/utils";
class BoxState {
text: string;
files: globalThis.File[];
constructor(text: string, files: globalThis.File[]) {
this.text = text;
this.files = files;
}
}
export class TypeBox {
static box = document.getElementById("typebox") as HTMLDivElement;
private static localuser?: Localuser;
static markdown = new MarkDown("");
private static files: globalThis.File[] = [];
private static imagesHtml = new WeakMap<Blob, HTMLElement>();
private static pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement;
private static nonceMap = new Map<string, string>();
static getNonce(id: string) {
const nonce = this.nonceMap.get(id) || Math.floor(Math.random() * 1000000000) + "";
this.nonceMap.set(id, nonce);
return nonce;
}
static focus() {
this.box.focus();
}
static init() {
this.box.addEventListener("keyup", this.handleEnter.bind(this));
this.box.addEventListener("keydown", (event) => {
if (event.isComposing) return;
this.localuser?.keydown(event);
if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) {
event.preventDefault();
event.stopImmediatePropagation();
}
});
this.markdown.giveBox(this.box);
const mobileSend = document.getElementById("mobileSend");
if (mobileSend) {
mobileSend.onclick = () => {
const channel = this.localuser?.focusChannel;
if (!channel) return;
const content = MarkDown.gatherBoxText(this.box);
this.sendMessage(channel, content);
};
}
const channelw = document.getElementById("channelw");
if (channelw)
channelw.addEventListener("keypress", (e) => {
if (e.ctrlKey || e.altKey || e.metaKey || e.metaKey) return;
let owner = e.target as HTMLElement;
while (owner !== channelw) {
if (owner.tagName === "input" || owner.contentEditable !== "false") {
return;
}
owner = owner.parentElement as HTMLElement;
}
this.markdown.boxupdate(Infinity);
});
document.addEventListener("paste", async (e: ClipboardEvent) => {
if (!this.localuser?.focusChannel) return;
if (!e.clipboardData) return;
for (const file of Array.from(e.clipboardData.files)) {
e.preventDefault();
this.addFile(file);
}
this.localuser?.updateSend();
});
let dragendtimeout = setTimeout(() => {});
document.addEventListener("dragover", (e) => {
clearTimeout(dragendtimeout);
const data = e.dataTransfer;
const bg = document.getElementById("gimmefile") as HTMLDivElement;
if (data) {
const isfile =
data.types.includes("Files") || data.types.includes("application/x-moz-file");
if (!isfile) {
bg.hidden = true;
return;
}
e.preventDefault();
bg.hidden = false;
//console.log(data.types,data)
} else {
bg.hidden = true;
}
});
document.addEventListener("dragleave", (_) => {
dragendtimeout = setTimeout(() => {
const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden = true;
}, 1000);
});
document.addEventListener("dragenter", (e) => {
e.preventDefault();
});
document.addEventListener("drop", (e) => {
const data = e.dataTransfer;
const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden = true;
if (!this.localuser?.focusChannel) {
e.preventDefault();
return;
}
if (data) {
const isfile =
data.types.includes("Files") || data.types.includes("application/x-moz-file");
if (isfile) {
e.preventDefault();
console.log(data.files);
for (const file of Array.from(data.files)) {
this.addFile(file);
}
this.localuser?.updateSend();
}
}
});
}
private static cMap = new Map<string, BoxState>();
private static channelToID(c: Channel) {
return c.id;
}
static saveBox() {
if (!this.localuser?.focusChannel) return;
const state = new BoxState(MarkDown.gatherBoxText(this.box), this.files);
this.pasteImageElement.textContent = "";
this.box.textContent = "";
this.files = [];
this.markdown.txt = [];
this.cMap.set(this.channelToID(this.localuser.focusChannel), state);
}
static restoreBox(c = this.localuser?.focusChannel) {
if (!c) return;
const state = this.cMap.get(this.channelToID(c));
this.box.textContent = "";
this.pasteImageElement.textContent = "";
if (state) {
this.markdown.txt = [...state.text];
this.files = state.files;
} else {
this.markdown.txt = [];
this.files = [];
}
this.box.append(this.markdown.makeHTML({keep: true}));
this.markdown.owner = c;
this.markdown.boxupdate(Infinity);
for (const file of this.files) {
this.addFile(file, false);
}
}
static changeWrite() {
if (!this.localuser?.focusChannel) return;
const c = this.localuser.focusChannel;
const canMessage = c.canMessage;
try {
this.box.contentEditable = canMessage ? "plaintext-only" : "false";
} catch {
this.box.contentEditable = canMessage ? "true" : "false";
}
(document.getElementById("upload") as HTMLElement).style.visibility = canMessage
? "visible"
: "hidden";
(document.getElementById("gifTB") as HTMLElement).style.display = canMessage ? "block" : "none";
(document.getElementById("stickerTB") as HTMLElement).style.display = canMessage
? "block"
: "none";
(document.getElementById("emojiTB") as HTMLElement).style.display = canMessage
? "block"
: "none";
(document.getElementById("mobileSend") as HTMLElement).style.display = canMessage
? "block"
: "none";
(document.getElementById("typediv") as HTMLElement).style.visibility = "visible";
if (!mobile) {
this.box.focus();
} else {
this.box.blur();
}
}
static saveCarrot() {
return saveCaretPosition(this.box);
}
static updateSend() {
if (
(this.markdown.rawString && this.markdown.rawString !== "\n") ||
document.getElementById("pasteimage")?.children.length
) {
this.box.parentElement!.classList.remove("noConent");
} else {
this.box.parentElement!.classList.add("noConent");
}
}
static changeVisablity(visable: boolean) {
if (visable) {
} else {
this.box.contentEditable = "" + false;
const replybox = document.getElementById("replybox") as HTMLElement;
replybox.classList.add("hideReplyBox");
this.box.classList.remove("typeboxreplying");
(document.getElementById("upload") as HTMLElement).style.visibility = "hidden";
(document.getElementById("typediv") as HTMLElement).style.visibility = "hidden";
(document.getElementById("sideDiv") as HTMLElement).innerHTML = "";
}
}
static addFile(blob: globalThis.File, add = true) {
const file = File.initFromBlob(blob);
const html = file.upHTML(this.files, this.imagesHtml, blob, () => {
this.localuser?.updateSend();
});
this.pasteImageElement.appendChild(html);
if (add) this.files.push(blob);
this.imagesHtml.set(blob, html);
}
static updateReplying() {
const c = this.localuser?.focusChannel;
const replybox = document.getElementById("replybox") as HTMLElement;
if (c && c.replyingto) {
this.box.classList.add("typeboxreplying");
replybox.innerHTML = "";
const span = document.createElement("span");
span.textContent = I18n.replyingTo(c.replyingto.author.username);
const X = document.createElement("button");
X.onclick = (_) => {
if (c.replyingto?.div) {
c.replyingto.div.classList.remove("replying");
}
replybox.classList.add("hideReplyBox");
c.replyingto = null;
replybox.innerHTML = "";
TypeBox.updateReplying();
};
replybox.classList.remove("hideReplyBox");
X.classList.add("cancelReply", "svgicon", "svg-x");
replybox.append(span);
replybox.append(X);
} else {
replybox.classList.add("hideReplyBox");
replybox.innerHTML = "";
this.box.classList.remove("typeboxreplying");
}
}
static uploadFiles() {
const input = document.createElement("input");
input.type = "file";
input.click();
input.multiple = true;
console.log("clicked");
if (!this.localuser?.focusChannel) return;
input.onchange = () => {
if (input.files) {
for (const file of Array.from(input.files)) {
this.addFile(file);
}
this.localuser?.updateSend();
}
};
}
static regSwap(l: Localuser) {
l.onswap = (l) => {
this.localuser = l;
this.regSwap(l);
};
this.localuser = l;
}
private static async handleEnter(event: KeyboardEvent): Promise<void> {
if (event.isComposing) return;
if (event.key === "Escape" && (this.files.length || this.localuser?.focusChannel?.replyingto)) {
while (this.files.length) {
const elm = this.imagesHtml.get(this.files.pop() as Blob) as HTMLElement;
if (this.pasteImageElement.contains(elm)) this.pasteImageElement.removeChild(elm);
}
if (this.localuser?.focusChannel) {
this.localuser.focusChannel?.replyingto?.div?.classList.remove("replying");
this.localuser.focusChannel.replyingto = null;
this.localuser.focusChannel.makereplybox();
}
this.localuser?.updateSend();
return;
}
if (this.localuser?.handleKeyUp(event)) {
return;
}
const channel = this.localuser?.focusChannel;
if (!channel) return;
const content = MarkDown.gatherBoxText(this.box);
if (content === "" && event.key === "ArrowUp") {
channel.editLast();
return;
}
channel.typingstart();
if (event.key === "Enter" && !event.shiftKey && window.innerWidth > 600) {
event.preventDefault();
await this.sendMessage(channel, content);
}
}
private static async sendMessage(channel: Channel, content: string) {
if (!channel.canMessageRightNow()) return;
if (channel.curCommand) {
channel.submitCommand();
return;
}
this.markdown.onUpdate("", false);
let replyingTo = this.localuser?.focusChannel ? this.localuser.focusChannel.replyingto : null;
if (replyingTo?.div) {
replyingTo.div.classList.remove("replying");
}
if (this.localuser?.focusChannel) {
this.localuser.focusChannel.replyingto = null;
this.localuser.focusChannel.makereplybox();
}
const attachments = this.files.filter((_) => document.contains(this.imagesHtml.get(_) || null));
while (this.files.length) {
const elm = this.imagesHtml.get(this.files.pop() as Blob) as HTMLElement;
if (this.pasteImageElement.contains(elm)) this.pasteImageElement.removeChild(elm);
}
this.box.innerHTML = "";
this.markdown.txt = [];
try {
await new Promise<void>((mres, rej) =>
channel.sendMessage(
content,
{
attachments,
embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo,
sticker_ids: [],
//nonce: getNonce(channel.id),
},
(res) => {
if (res === "Ok") {
mres();
} else {
rej();
}
},
),
);
} catch {
this.files = attachments;
for (const file of this.files) {
const img = this.imagesHtml.get(file);
if (!img) continue;
this.pasteImageElement.append(img);
}
channel.replyingto = replyingTo;
channel.makereplybox();
this.box.textContent = content;
this.markdown.txt = content.split("");
this.markdown.boxupdate(Infinity);
}
this.nonceMap.delete(channel.id);
}
}
TypeBox.init();
+7 -2
View File
@@ -803,7 +803,7 @@ class User extends SnowFlake {
createWidget(guild?: Guild) {
guild = this.localuser.guilds.get("@me") as Guild;
const div = document.createElement("div");
div.classList.add("flexltr", "createdWebhook");
div.classList.add("mutFriends", "flexltr");
//TODO make sure this is something I can actually do here
const name = document.createElement("b");
name.textContent = this.name;
@@ -1447,7 +1447,7 @@ class User extends SnowFlake {
};
(async () => {
const high = await this.highInfo();
const mut = buttons.add(I18n.profile.mut());
const mut = buttons.add(I18n.profile.mut(high.mutual_guilds.length + ""));
const mutDiv = document.createElement("div");
mutDiv.append(
@@ -1466,6 +1466,11 @@ class User extends SnowFlake {
gname.textContent = guild.properties.name;
info.append(gname);
box.append(icon, info);
box.onclick = () => {
removeAni(background);
guild.loadGuild();
guild.loadChannel();
};
if (nick) info.append(nick);
return box;
})
+2 -1
View File
@@ -36,13 +36,14 @@ export class UserPreferences {
emojiFont?: string;
checkMemberList = false;
gifProvidor?: string;
showToday = true;
constructor(init?: Partial<UserPreferences>) {
Object.assign(this, init);
}
}
export async function getPreferences(): Promise<UserPreferences> {
export function getPreferences(): UserPreferences {
return new UserPreferences(JSON.parse(localStorage.getItem("userPreferences") || "{}"));
}
+15 -17
View File
@@ -32,7 +32,7 @@ let instances:
| null = null;
await setTheme();
export async function setTheme(theme?: string) {
const prefs = await getPreferences();
const prefs = getPreferences();
document.body.className = (theme || prefs.theme) + "-theme";
console.log(theme);
}
@@ -87,9 +87,9 @@ export function setDefaults() {
userinfos.accent_color = "#3096f7";
}
getPreferences().then((perfs) =>
document.documentElement.style.setProperty("--accent-color", perfs.accentColor),
);
const perfs = getPreferences();
document.documentElement.style.setProperty("--accent-color", perfs.accentColor);
if (userinfos.preferences === undefined) {
userinfos.preferences = {
theme: "Dark",
@@ -749,14 +749,12 @@ export function createImg(
elm: HTMLElement | void,
type: "gif" | "icon" = "gif",
): safeImg {
const aniOpt = getPreferences().then((prefs) => {
return (
(type === "gif" ? prefs.animateGifs : prefs.animateIcons) ||
("hover" as "hover") ||
"always" ||
"never"
);
});
const prefs = getPreferences();
const aniOpt =
(type === "gif" ? prefs.animateGifs : prefs.animateIcons) ||
("hover" as "hover") ||
"always" ||
"never";
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";
@@ -769,11 +767,11 @@ export function createImg(
if (animated) {
img.crossOrigin = "anonymous";
}
img.src = (await aniOpt) !== "always" ? staticsrc || src || "" : src || "";
img.src = aniOpt !== "always" ? staticsrc || src || "" : src || "";
});
}
img.onload = async () => {
if ((await aniOpt) === "always") return;
if (aniOpt === "always") return;
if (!src) return;
if ((await isAnimated(src)) && !staticsrc) {
let s = staticImgMap.get(src);
@@ -797,13 +795,13 @@ export function createImg(
}
};
elm.addEventListener("mouseover", async () => {
if ((await aniOpt) === "never") return;
if (aniOpt === "never") return;
if (img.src !== src && src) {
img.src = src;
}
});
elm.addEventListener("mouseleave", async () => {
if (staticsrc && (await aniOpt) !== "always") {
if (staticsrc && aniOpt !== "always") {
img.src = staticsrc;
}
});
@@ -817,7 +815,7 @@ export function createImg(
if (animated) {
img.crossOrigin = "anonymous";
}
img.src = (await aniOpt) !== "always" ? staticsrc || src || "" : src || "";
img.src = aniOpt !== "always" ? staticsrc || src || "" : src || "";
});
}
},
+15 -4
View File
@@ -13,6 +13,12 @@
"whatelse": "What else do you think should happen?",
"whereever": "Wherever this is"
},
"filesize": {
"B": "B",
"KB": "KB",
"MB": "MB",
"GB": "GB"
},
"@metadata": {
"authors": ["MathMan05, TheGeekn°72"],
"comment": "Don't know how often I'll update this top part lol // Aye, I'll take care of it",
@@ -33,6 +39,7 @@
"DMs": {
"add": "Add someone to this DM",
"close": "Close DM",
"copyURL":"Copy DM URL",
"copyId": "Copy DM id",
"markRead": "Mark as read",
"name": "Direct Messages"
@@ -124,6 +131,7 @@
"cancel": "Cancel",
"channel": {
"SlowmodeCool": "Slowmode cooldown: $1",
"copyURL":"Copy channel URL",
"TimeOutCool": "Timed out until: $1",
"allowIcons": "Allow custom channel icons",
"announcement": "Announcements",
@@ -151,7 +159,7 @@
"text": "Text",
"timedOutUntil": "Timed out until: $1",
"topic:": "Topic:",
"typebox": "Message in $1",
"typebox": "Message in # $1",
"unmute": "Unmute channel",
"voice": "Voice",
"deleteThread":"Delete thread",
@@ -740,7 +748,8 @@
"REQUEST_TO_SPEAK": "Allows role members to request to speak in stage channels.",
"USE_EMBEDDED_ACTIVITIES": "Allows role members to use embedded activities.",
"USE_APPLICATION_COMMANDS": "Allows role members to use application commands.",
"USE_EXTERNAL_APPS": "Allows role members to have application responses to show publicly in channel (When disabled, users will still be allowed to use their apps but responses will be visible only to themselves. This only applies to apps not also installed to the guild)."
"USE_EXTERNAL_APPS": "Allows role members to have application responses to show publicly in channel (When disabled, users will still be allowed to use their apps but responses will be visible only to themselves. This only applies to apps not also installed to the guild).",
"SET_VOICE_CHANNEL_STATUS":"Set voice channel status"
},
"readableNames": {
"ADD_REACTIONS": "Add reactions",
@@ -793,7 +802,8 @@
"VIEW_AUDIT_LOG": "View audit log",
"VIEW_CHANNEL": "View channels",
"VIEW_CREATOR_MONETIZATION_ANALYTICS": "View creator monetization analytics",
"VIEW_GUILD_INSIGHTS": "View guild insights"
"VIEW_GUILD_INSIGHTS": "View guild insights",
"SET_VOICE_CHANNEL_STATUS":"Set voice channel status"
}
},
"pinMessage": "Pin message",
@@ -801,12 +811,13 @@
"bio": "About me:",
"joined": "Account made: $1",
"joinedMember": "Joined $1: $2",
"mut": "Mutual guilds",
"mut": "Mutual guilds ($1)",
"mutFriends": "Mutual friends",
"permInfo": "Permissions",
"userInfo": "User info",
"notes":"User notes"
},
"messageNotLoad":"Messages failed to load",
"profileColor": "Profile color",
"pronouns": "Pronouns:",
"readableName": "English",
+26 -9
View File
@@ -52,6 +52,7 @@
"enable": "Activer l'introduction",
"title": "Bienvenue sur $1 !"
},
"copyMedia": "Copier L'URL du média",
"DMs": {
"add": "Ajouter quelqu'un à la discussion privée (MP)",
"close": "Fermer la discussion par messages privés",
@@ -123,7 +124,7 @@
"hypesquad_house_1": "Audace",
"hypesquad_house_2": "Excellence",
"hypesquad_house_3": "Équilibre",
"legacy_username": "a un nom d'utilisateur d'origine",
"legacy_username": "a un nom d'utilisateur hérité",
"partner": "Partenaire de l'instance",
"premium": "Premium",
"quest_completed": "a fait une quête",
@@ -149,17 +150,17 @@
"TimeOutCool": "Exclu jusqu'à : $1",
"allowIcons": "Autoriser les icônes de chaîne personnalisées",
"announcement": "Annonces",
"copyId": "Copier l'id du canal",
"copyIdCat": "Copier l'id de la catégorie",
"copyId": "Copier l'identifiant de la chaîne",
"copyIdCat": "Copier l'identifiant de la catégorie",
"createCatagory": "Créer une catégorie",
"createChannel": "Créer un canal",
"creating": "Création du canal",
"createChannel": "Créer un chaîne",
"creating": "Création du Chaîne",
"delete": "Supprimer la chaîne",
"deleteCat": "Supprimer la catégorie",
"icon": "Icône :",
"makeInvite": "Créer une invitation",
"markRead": "Marquer comme lu",
"mute": "Rendre le canal muet",
"mute": "Rendre la chaîne muette",
"name": "Chaîne",
"name:": "Nom de la Chaîne :",
"nsfw:": "Avertissement contenu mature :",
@@ -268,7 +269,7 @@
"region": "Région : $1",
"title": "Gérer les sessions"
},
"discovery": "Portail de Découverte (Discovery)",
"discovery": "Portail de Découverte",
"dms": "Messages privés",
"edit": "Modifier",
"emoji": {
@@ -330,7 +331,7 @@
"guild": {
"COMMUNITY": "Demander à rejoindre",
"disableInvites": "Désactiver les invitations :",
"DISCOVERABLE": "Visible depuis le Portail de Découverte (Discovery)",
"DISCOVERABLE": "Visible depuis le Portail de Découverte",
"INVITES_DISABLED": "Sur invitation uniquement",
"adminMenu": {
"changePerms": "Modifier les permissions pour rechercher",
@@ -457,7 +458,7 @@
"userField": "Nom dutilisateur:",
"welcomeJank": "Bienvenue dans le client Fermi"
},
"incorrectURLS": "## Cette instance a probablement envoyé des liens incorrects.\n### Si vous êtes le propriétaire de linstance, veuillez consulter la section *Connecting from remote machines* de [cette page](https://docs.spacebar.chat/setup/server/) pour corriger le problème.\n Souhaitez-vous que le client Fermi tente automatiquement de corriger cette erreur pour vous permettre de vous connecter à linstance ?",
"incorrectURLS": "## Cette instance a probablement envoyé des liens incorrects.\n### Si vous êtes le propriétaire de linstance, veuillez consulter la section *Connecting from remote machines* de [cette page](https://docs.melodychat.org/setup/server/) pour corriger le problème.\nSouhaitez-vous que le client Fermi tente automatiquement de corriger cette erreur pour vous permettre de vous connecter à linstance ?",
"instInfo": "Informations dinstance",
"instanceInfo": {
"contact": "Envoyer un e-mail aux administrateurs de linstance",
@@ -486,6 +487,7 @@
"invite": {
"accept": "Accepter",
"alreadyJoined": "Déjà rejoint",
"joining": "Rejoindre...",
"channel:": "Canal :",
"createInvite": "Créer une invitation",
"createdAt": "Créée le : $1",
@@ -1011,6 +1013,21 @@
"resolving": "résolution de l'utilisateur",
"unknown": "@utilisateur-inconnu"
},
"editMode": {
"editMsg": "Échapper à $1cancel$1"
},
"keyboard": {
"shortcuts": "Raccourcis",
"empty": "<No keybind set>",
"descs": {
"gifSearch": "Rechercher des GIFs :"
}
},
"domain": {
"title": "Vérifier le domaine",
"domain": "Domaine :",
"dnsinst": "1. Connectez-vous à votre fournisseur DNS\n\n2. Créer un registre DNS : \n\nNom :\n```\n_harmony.$1\n```\n\nType :\n```\nTXT\n```\n\nContenu :\n```\n$2\n```\n\n-# Il faudra probablement quelques minutes pour le DNS se propage."
},
"vc": {
"joinForStream": "Rejoignez le canal vocal pour regarder",
"joiningStream": "Connexion à la diffusion en cours…",
+12 -3
View File
@@ -32,6 +32,7 @@
"desc": "Descrizione:",
"title": "Benvenuto a $1!"
},
"copyMedia": "Copia URL del contenuto multimediale",
"DMs": {
"add": "Aggiungi qualcuno a questo DM",
"close": "Chiudi DM",
@@ -149,7 +150,7 @@
"text": "Testo",
"timedOutUntil": "In time-out fino: $1",
"topic:": "Argomento:",
"typebox": "Messaggio in $1",
"typebox": "Messaggio in # $1",
"unmute": "Riattiva canale",
"voice": "Voce",
"deleteThread": "Elimina thread",
@@ -594,14 +595,16 @@
"PIN_MESSAGES": "Consente ai membri del ruolo di fissare messaggi.",
"MANAGE_EVENTS": "Consente ai membri del ruolo di modificare/cancellare eventi (esistenti e futuri).",
"SEND_VOICE_MESSAGES": "Consente l'invio di messaggi vocali nei canali di testo.",
"CREATE_INSTANT_INVITE": "Consente ai membri del ruolo di creare inviti per la gilda."
"CREATE_INSTANT_INVITE": "Consente ai membri del ruolo di creare inviti per la gilda.",
"SET_VOICE_CHANNEL_STATUS": "Imposta lo stato del canale vocale"
},
"readableNames": {
"BYPASS_SLOWMODE": "Bypassa la slowmode",
"CONNECT": "Connetti",
"PIN_MESSAGES": "Fissa messaggi",
"SEND_MESSAGES": "Invia messaggi",
"STREAM": "Video"
"STREAM": "Video",
"SET_VOICE_CHANNEL_STATUS": "Imposta lo stato del canale vocale"
}
},
"profile": {
@@ -692,6 +695,12 @@
"userping": {
"unknown": "@utente-sconosciuto"
},
"keyboard": {
"shortcuts": "Tasti di scelta rapida",
"descs": {
"gifSearch": "Ricerca GIF:"
}
},
"domain": {
"title": "Verifica dominio",
"domain": "Dominio:"
+5
View File
@@ -34,6 +34,7 @@
"enable": "온보딩 활성화",
"title": "$1에 오신 것을 환영합니다!"
},
"copyMedia": "미디어 URL 복사",
"DMs": {
"add": "이 메시지에 누군가를 추가",
"close": "DM 닫기",
@@ -468,6 +469,7 @@
"invite": {
"accept": "수락",
"alreadyJoined": "이미 참여했습니다",
"joining": "가입하는 중...",
"channel:": "채널:",
"createInvite": "초대장 만들기",
"createdAt": "만든 날짜: $1",
@@ -993,6 +995,9 @@
"resolving": "사용자 해결 중",
"unknown": "@unknown-user"
},
"keyboard": {
"shortcuts": "단축키"
},
"vc": {
"joinForStream": "음성 채널에 참여해 시청",
"joiningStream": "스트리밍에 참여 중...",
+1
View File
@@ -17,6 +17,7 @@
"desc": "Beschreiwung:",
"title": "Wëllkomm op $1!"
},
"copyMedia": "Medien-URL kopéieren",
"DMs": {
"markRead": "Als gelies markéieren"
},
+12
View File
@@ -31,6 +31,7 @@
"enable": "Inwerken inschakelen",
"title": "Welkom bij $1!"
},
"copyMedia": "Media-url kopiëren",
"DMs": {
"add": "Iemand aan dit directe bericht toevoegen",
"close": "Directe bericht sluiten",
@@ -465,6 +466,7 @@
"invite": {
"accept": "Accepteren",
"alreadyJoined": "Al lid",
"joining": "Deelnemen...",
"channel:": "Kanaal:",
"createInvite": "Uitnodiging maken",
"createdAt": "Gemaakt op $1",
@@ -990,6 +992,16 @@
"resolving": "gebruiken aan het opzoeken",
"unknown": "@onbekende-gebruiker"
},
"editMode": {
"editMsg": "Escape om te $1annuleren$1"
},
"keyboard": {
"shortcuts": "Sneltoetsen",
"empty": "<Geen toetsencombinatie ingesteld>",
"descs": {
"gifSearch": "GIF's zoeken:"
}
},
"domain": {
"title": "Domein verifiëren",
"domain": "Domein:",
+6
View File
@@ -25,6 +25,12 @@
"whatelse": "Easter egg alert message shown randomly when clicking the 'Wherever this is' link on the 404 page. It's meant to be humorous/silly.",
"whereever": "Link text on the 404 page that triggers random easter egg events when clicked."
},
"filesize": {
"B": "SI unit bytes abbreviated",
"KB": "SI unit kilobytes abbreviated",
"MB": "SI unit megabytes abbreviated",
"GB": "SI unit gigabytes abbreviated"
},
"onboarding": {
"name": "Settings section title for the guild onboarding feature.",
"disable": "Button label to disable the onboarding feature for a guild.",
+3
View File
@@ -988,6 +988,9 @@
"resolving": "zisťuje sa používateľ",
"unknown": "@neznámy-používateľ"
},
"editMode": {
"editMsg": "Escapovať na $1cancel$1"
},
"domain": {
"title": "Overiť doménu",
"domain": "Doména:",
+17 -3
View File
@@ -39,6 +39,7 @@
"enable": "启用入门",
"title": "欢迎来到$1"
},
"copyMedia": "复制媒体 URL",
"DMs": {
"add": "将某人添加到此私信",
"close": "关闭私信",
@@ -160,7 +161,7 @@
"text": "文字",
"timedOutUntil": "超时至:$1",
"topic:": "主题:",
"typebox": "在$1的消息",
"typebox": "在 # $1 的消息",
"unmute": "取消静音频道",
"voice": "语音",
"deleteThread": "删除帖子",
@@ -473,6 +474,7 @@
"invite": {
"accept": "接受",
"alreadyJoined": "已加入",
"joining": "正在加入…",
"channel:": "频道:",
"createInvite": "创建邀请",
"createdAt": "创建于 $1",
@@ -748,7 +750,8 @@
"REQUEST_TO_SPEAK": "允许该身份组成员请求在表演频道发言。",
"USE_EMBEDDED_ACTIVITIES": "允许该身份组成员使用嵌入式活动。",
"USE_APPLICATION_COMMANDS": "允许该身份组成员使用应用程序命令。",
"USE_EXTERNAL_APPS": "允许该身份组成员以应用程序产生的回复在频道中公开显示(禁用此功能后,用户仍可使用其应用,但回复将仅他们自己可见。此功能仅适用于未安装到公会的应用程序)。"
"USE_EXTERNAL_APPS": "允许该身份组成员以应用程序产生的回复在频道中公开显示(禁用此功能后,用户仍可使用其应用,但回复将仅他们自己可见。此功能仅适用于未安装到公会的应用程序)。",
"SET_VOICE_CHANNEL_STATUS": "设置语音频道状态"
},
"readableNames": {
"ADD_REACTIONS": "添加反应",
@@ -801,7 +804,8 @@
"VIEW_AUDIT_LOG": "查看审计日志",
"VIEW_CHANNEL": "查看频道",
"VIEW_CREATOR_MONETIZATION_ANALYTICS": "查看创建者变现分析",
"VIEW_GUILD_INSIGHTS": "查看公会洞见"
"VIEW_GUILD_INSIGHTS": "查看公会洞见",
"SET_VOICE_CHANNEL_STATUS": "设置语音频道状态"
}
},
"pinMessage": "固定消息",
@@ -998,6 +1002,16 @@
"resolving": "正在解析用户",
"unknown": "@未知用户"
},
"editMode": {
"editMsg": "按下“Esc”以$1取消$1"
},
"keyboard": {
"shortcuts": "快捷键",
"empty": "<未设置键位绑定>",
"descs": {
"gifSearch": "搜索 GIF"
}
},
"domain": {
"title": "验证域名",
"domain": "域名:",