mirror of
https://github.com/MathMan05/Fermi.git
synced 2026-08-29 01:18:44 +00:00
varrious fixes and improvements
This commit is contained in:
+53
-7
@@ -32,6 +32,7 @@ import {CustomHTMLDivElement} from "./index.js";
|
||||
import {Direct} from "./direct.js";
|
||||
import {ProgessiveDecodeJSON} from "./utils/progessiveLoad.js";
|
||||
import {NotificationHandler} from "./notificationHandler.js";
|
||||
import {Command} from "./interactions/commands.js";
|
||||
|
||||
class Channel extends SnowFlake {
|
||||
editing!: Message | null;
|
||||
@@ -1578,6 +1579,38 @@ class Channel extends SnowFlake {
|
||||
this.textSave = MarkDown.gatherBoxText(typebox);
|
||||
typebox.textContent = "";
|
||||
}
|
||||
curCommand?: Command;
|
||||
curWatch = () => {};
|
||||
async submitCommand() {
|
||||
if (!this.curCommand) return;
|
||||
if (await this.curCommand.submit(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);
|
||||
}
|
||||
}
|
||||
startCommand(command: Command) {
|
||||
this.curCommand = command;
|
||||
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
|
||||
typebox.markdown.boxEnabled = false;
|
||||
const func = () => {
|
||||
const node = window.getSelection()?.focusNode;
|
||||
if (this.localuser.channelfocus === this) {
|
||||
const out = command.collect(typebox, this, node || undefined);
|
||||
if (!out) {
|
||||
typebox.markdown.boxEnabled = true;
|
||||
typebox.markdown.boxupdate();
|
||||
typebox.removeEventListener("keyup", func);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.curWatch = func;
|
||||
typebox.addEventListener("keyup", func);
|
||||
command.render(typebox, this);
|
||||
}
|
||||
async getHTML(addstate = true, getMessages: boolean | void = undefined, aroundMessage?: string) {
|
||||
if (this.owner instanceof Direct) {
|
||||
this.owner.freindDiv?.classList.remove("viewChannel");
|
||||
@@ -1586,11 +1619,17 @@ class Channel extends SnowFlake {
|
||||
this.localuser.channelfocus.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)));
|
||||
const md = typebox.markdown;
|
||||
md.owner = this;
|
||||
typebox.textContent = this.textSave;
|
||||
md.boxupdate(Infinity);
|
||||
if (!this.curCommand) {
|
||||
const md = typebox.markdown;
|
||||
md.owner = this;
|
||||
typebox.textContent = this.textSave;
|
||||
md.boxupdate(Infinity);
|
||||
}
|
||||
this.localuser.fileExtange(this.files, this.htmls);
|
||||
|
||||
if (getMessages === undefined) {
|
||||
@@ -1843,7 +1882,7 @@ class Channel extends SnowFlake {
|
||||
headers: this.headers,
|
||||
});
|
||||
|
||||
const response = await j.json();
|
||||
const response = (await j.json()) as messagejson[];
|
||||
if (response.length !== 100) {
|
||||
this.allthewayup = true;
|
||||
}
|
||||
@@ -1859,6 +1898,10 @@ class Channel extends SnowFlake {
|
||||
}
|
||||
prev = message;
|
||||
}
|
||||
if (!response.length) {
|
||||
this.lastmessageid = undefined;
|
||||
this.lastreadmessageid = undefined;
|
||||
}
|
||||
}
|
||||
delChannel(json: channeljson) {
|
||||
const build: Channel[] = [];
|
||||
@@ -1872,13 +1915,16 @@ class Channel extends SnowFlake {
|
||||
afterProm?: Promise<void>;
|
||||
afterProms = new Map<string, () => void>();
|
||||
async grabAfter(id: string) {
|
||||
if (this.idToNext.has(id)) {
|
||||
return;
|
||||
}
|
||||
if (id === this.lastmessage?.id) {
|
||||
return;
|
||||
}
|
||||
if (this.afterProm) return this.afterProm;
|
||||
if (this.afterProm) return new Promise<void>((res) => this.afterProms.set(id, res));
|
||||
let tempy: string | undefined = id;
|
||||
while (tempy && tempy.includes("fake")) {
|
||||
tempy = this.idToNext.get(tempy);
|
||||
tempy = this.idToPrev.get(tempy);
|
||||
}
|
||||
if (!tempy) return;
|
||||
id = tempy;
|
||||
|
||||
+50
-5
@@ -18,6 +18,8 @@ import {
|
||||
templateSkim,
|
||||
mute_config,
|
||||
GuildOverrides,
|
||||
commandJson,
|
||||
applicationJson,
|
||||
} from "./jsontypes.js";
|
||||
import {User} from "./user.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
@@ -27,6 +29,7 @@ import {createImg} from "./utils/utils.js";
|
||||
import {Sticker} from "./sticker.js";
|
||||
import {ProgessiveDecodeJSON} from "./utils/progessiveLoad.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
import {Command} from "./interactions/commands.js";
|
||||
export async function makeInviteMenu(inviteMenu: Options, guild: Guild, url: string) {
|
||||
const invDiv = document.createElement("div");
|
||||
const bansp = ProgessiveDecodeJSON<invitejson[]>(url, {
|
||||
@@ -1289,7 +1292,7 @@ class Guild extends SnowFlake {
|
||||
}
|
||||
this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel);
|
||||
this.stickers = json.stickers.map((_) => new Sticker(_, this)) || [];
|
||||
//this.getCommands();
|
||||
this.getCommands();
|
||||
}
|
||||
get perminfo() {
|
||||
return this.localuser.perminfo.guilds[this.id];
|
||||
@@ -1849,14 +1852,56 @@ class Guild extends SnowFlake {
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
commands?: Command[];
|
||||
commandProm?: Promise<unknown>;
|
||||
apps?: applicationJson[];
|
||||
async getApps() {
|
||||
if (this.commandProm) {
|
||||
await this.commandProm;
|
||||
}
|
||||
if (this.apps) {
|
||||
return this.commands;
|
||||
} else {
|
||||
const prom = this.getCommandsFetch();
|
||||
this.commandProm = prom;
|
||||
const {apps, commands} = await prom;
|
||||
this.commands = commands;
|
||||
this.apps = apps;
|
||||
return apps;
|
||||
}
|
||||
}
|
||||
async getCommands() {
|
||||
const json = await (
|
||||
if (this.commandProm) {
|
||||
await this.commandProm;
|
||||
}
|
||||
if (this.commands) {
|
||||
return this.commands;
|
||||
} else {
|
||||
const prom = this.getCommandsFetch();
|
||||
this.commandProm = prom;
|
||||
const {apps, commands} = await prom;
|
||||
this.commands = commands;
|
||||
this.apps = apps;
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
async getCommandsFetch() {
|
||||
const json = (await (
|
||||
await fetch(this.info.api + `/guilds/${this.id}/application-command-index`, {
|
||||
headers: this.headers,
|
||||
})
|
||||
).json();
|
||||
if (this.id === "1006649183970562092") console.warn(json.application_commands);
|
||||
).json()) as {application_commands: commandJson[]; applications: applicationJson[]};
|
||||
//TODO remove this fix once the server fixes this
|
||||
json.applications.forEach((_) => {
|
||||
if (_.icon && _.icon.startsWith("data")) {
|
||||
_.icon = null;
|
||||
}
|
||||
});
|
||||
return {
|
||||
apps: json.applications,
|
||||
commands: json.application_commands.map((_) => new Command(_, this.localuser)),
|
||||
};
|
||||
}
|
||||
}
|
||||
Guild.setupcontextmenu();
|
||||
|
||||
@@ -154,6 +154,10 @@ async function handleEnter(event: KeyboardEvent): Promise<void> {
|
||||
channel.typingstart();
|
||||
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
if (channel.curCommand) {
|
||||
channel.submitCommand();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
replyingTo = thisUser.channelfocus ? thisUser.channelfocus.replyingto : null;
|
||||
if (replyingTo?.div) {
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
import {Channel} from "../channel.js";
|
||||
import {Guild} from "../guild.js";
|
||||
import {I18n} from "../i18n.js";
|
||||
import {commandJson, commandOptionJson} from "../jsontypes.js";
|
||||
import {Localuser} from "../localuser.js";
|
||||
import {SnowFlake} from "../snowflake.js";
|
||||
function focusInput(html: HTMLElement) {
|
||||
const input = html.getElementsByTagName("input")[0];
|
||||
if (input) input.focus();
|
||||
}
|
||||
function focusElm(node: HTMLElement | Text, before = true) {
|
||||
const selection = window.getSelection();
|
||||
if (!selection) return;
|
||||
var range = document.createRange();
|
||||
if (before) {
|
||||
range.setStartBefore(node);
|
||||
} else {
|
||||
range.setStartAfter(node);
|
||||
}
|
||||
range.collapse(true);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
export class Command extends SnowFlake {
|
||||
owner: Localuser | Guild;
|
||||
type: 1 | 2 | 3 | 4;
|
||||
applicationId: string;
|
||||
name: string;
|
||||
nameLocalizations: Record<string, string>;
|
||||
descriptionLocalizations: Record<string, string>;
|
||||
description: string;
|
||||
defaultMemberPerms: BigInt;
|
||||
permissions: {
|
||||
user: boolean;
|
||||
roles: Record<string, boolean>;
|
||||
channels: Record<string, boolean>;
|
||||
};
|
||||
nsfw: boolean;
|
||||
gpr: number;
|
||||
version: string;
|
||||
handler: 1 | 2 | 3;
|
||||
options: Option[];
|
||||
readonly rawJson: Readonly<commandJson>;
|
||||
get localuser() {
|
||||
if (this.owner instanceof Localuser) {
|
||||
return this.owner;
|
||||
} else {
|
||||
return this.owner.owner;
|
||||
}
|
||||
}
|
||||
constructor(command: commandJson, owner: Localuser | Guild) {
|
||||
super(command.id);
|
||||
this.rawJson = Object.freeze(structuredClone(command));
|
||||
this.owner = owner;
|
||||
this.type = command.type;
|
||||
this.applicationId = command.application_id;
|
||||
this.name = command.name;
|
||||
this.nameLocalizations = command.name_localizations || {};
|
||||
this.description = command.description;
|
||||
this.descriptionLocalizations = command.description_localizations || {};
|
||||
this.defaultMemberPerms = BigInt(command.default_member_permissions || "0");
|
||||
this.permissions = {
|
||||
user: command.permissions?.user || true,
|
||||
roles: command.permissions?.roles || {},
|
||||
channels: command.permissions?.channels || {},
|
||||
};
|
||||
command.options ||= [];
|
||||
this.options = command.options.map((_) => Option.toOption(_, this));
|
||||
this.nsfw = command.nsfw;
|
||||
this.gpr = command.global_popularity_rank || 0;
|
||||
this.version = command.version;
|
||||
this.handler = command.handler || 1;
|
||||
}
|
||||
get localizedName() {
|
||||
return this.nameLocalizations[I18n.lang] || this.name;
|
||||
}
|
||||
get localizedDescription() {
|
||||
return this.descriptionLocalizations[I18n.lang] || this.description;
|
||||
}
|
||||
similar(search: string) {
|
||||
if (search.length === 0) {
|
||||
return 0.1;
|
||||
}
|
||||
const similar = (str: string) => {
|
||||
if (str.includes(search)) {
|
||||
return search.length / str.length;
|
||||
} else if (str.toLowerCase().includes(search.toLowerCase())) {
|
||||
return str.length / str.length / 1.4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
return Math.max(
|
||||
similar(this.name),
|
||||
similar(this.description),
|
||||
similar(this.localizedDescription),
|
||||
similar(this.localizedName),
|
||||
);
|
||||
}
|
||||
state = new WeakMap<
|
||||
Channel,
|
||||
(
|
||||
| {
|
||||
option: Option;
|
||||
state: string;
|
||||
}
|
||||
| string
|
||||
)[]
|
||||
>();
|
||||
collect(html: HTMLElement, channel: Channel, node?: Node): boolean {
|
||||
const states = this.state.get(channel);
|
||||
const build: (
|
||||
| {
|
||||
option: Option;
|
||||
state: string;
|
||||
}
|
||||
| string
|
||||
)[] = [];
|
||||
if (!states) return false;
|
||||
|
||||
let gotname = false;
|
||||
for (const elm of Array.from(html.childNodes)) {
|
||||
if (elm instanceof HTMLElement) {
|
||||
if (elm.classList.contains("commandFront")) {
|
||||
gotname = true;
|
||||
continue;
|
||||
}
|
||||
const name = elm.getAttribute("commandName");
|
||||
const state = states.find((_) => _ instanceof Object && _.option.match(name || ""));
|
||||
if (state) {
|
||||
build.push(state);
|
||||
} else {
|
||||
const option = this.options.find((_) => _.match(name || ""));
|
||||
if (option) {
|
||||
build.push({option, state: ""});
|
||||
}
|
||||
}
|
||||
} else if (elm instanceof Text) {
|
||||
build.push(elm.textContent || "");
|
||||
}
|
||||
}
|
||||
|
||||
if (node instanceof Text) {
|
||||
this.searchAtr(node, channel, html);
|
||||
}
|
||||
|
||||
if (gotname) {
|
||||
this.state.set(channel, build);
|
||||
} else {
|
||||
this.state.delete(channel);
|
||||
}
|
||||
|
||||
return gotname;
|
||||
}
|
||||
searchAtr(textNode: Text, channel: Channel, Divhtml: HTMLElement) {
|
||||
const text = (textNode.textContent || "").trim();
|
||||
const states = this.state.get(channel);
|
||||
if (!states) {
|
||||
this.localuser.MDSearchOptions(
|
||||
[],
|
||||
"",
|
||||
document.getElementById("searchOptions") as HTMLDivElement,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const opts = this.options
|
||||
.filter((obj) => !states.find((_) => _ instanceof Object && _.option === obj))
|
||||
.map((opt) => [opt, opt.similar(text)] as const)
|
||||
.filter((_) => _[1])
|
||||
.sort((a, b) => a[1] - b[1])
|
||||
.slice(0, 6)
|
||||
.map((_) => _[0]);
|
||||
this.localuser.MDSearchOptions(
|
||||
opts.map((opt) => {
|
||||
return [
|
||||
opt.localizedName,
|
||||
"",
|
||||
void 0,
|
||||
() => {
|
||||
const html = opt.toHTML("", channel);
|
||||
textNode.after(html);
|
||||
textNode.remove();
|
||||
this.collect(Divhtml, channel);
|
||||
console.log(this.state.get(channel));
|
||||
focusInput(html);
|
||||
return true;
|
||||
},
|
||||
];
|
||||
}),
|
||||
"",
|
||||
document.getElementById("searchOptions") as HTMLDivElement,
|
||||
);
|
||||
}
|
||||
render(html: HTMLElement, channel: Channel) {
|
||||
html.innerHTML = "";
|
||||
let state = this.state.get(channel);
|
||||
if (!state) {
|
||||
const req = this.options.filter((_) => _.required);
|
||||
state = req.map((option) => ({option, state: ""}));
|
||||
this.state.set(channel, state);
|
||||
}
|
||||
const command = document.createElement("span");
|
||||
command.classList.add("commandFront");
|
||||
command.textContent = `/${this.localizedName}`;
|
||||
command.contentEditable = "false";
|
||||
html.append(command);
|
||||
let lastElm: HTMLElement | undefined = undefined;
|
||||
for (const thing of state) {
|
||||
if (typeof thing === "string") {
|
||||
html.append(thing);
|
||||
continue;
|
||||
}
|
||||
const {option, state} = thing;
|
||||
const opt = option.toHTML(state, channel);
|
||||
lastElm = opt;
|
||||
html.append(opt);
|
||||
}
|
||||
if (lastElm) {
|
||||
focusInput(lastElm);
|
||||
} else {
|
||||
const node = new Text();
|
||||
node.textContent = "";
|
||||
html.append(node);
|
||||
focusElm(node, false);
|
||||
}
|
||||
}
|
||||
stateChange(option: Option, channel: Channel, state: string) {
|
||||
const states = this.state.get(channel);
|
||||
if (!states) return;
|
||||
const stateObj = states.find((_) => _ instanceof Object && _.option === option);
|
||||
if (stateObj && stateObj instanceof Object) {
|
||||
stateObj.state = state;
|
||||
}
|
||||
}
|
||||
get info() {
|
||||
return this.owner.info;
|
||||
}
|
||||
|
||||
get headers() {
|
||||
return this.owner.headers;
|
||||
}
|
||||
|
||||
async submit(channel: Channel) {
|
||||
const nonce = Math.floor(Math.random() * 10 ** 9) + "";
|
||||
const states = this.state.get(channel);
|
||||
if (!states) {
|
||||
return true;
|
||||
}
|
||||
const opts = states.filter((_) => typeof _ !== "string");
|
||||
|
||||
await fetch(this.info.api + "/interactions", {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
type: 2,
|
||||
nonce: nonce,
|
||||
guild_id: channel.owner.id,
|
||||
channel_id: channel.id,
|
||||
application_id: this.applicationId,
|
||||
session_id: this.localuser.session_id,
|
||||
data: {
|
||||
application_command: this.rawJson,
|
||||
attachments: [],
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
options: opts.map(({option, state}) => {
|
||||
return option.toJson(state);
|
||||
}),
|
||||
type: 1,
|
||||
version: this.version,
|
||||
},
|
||||
}),
|
||||
});
|
||||
this.state.delete(channel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
abstract class Option {
|
||||
type: number;
|
||||
required: boolean;
|
||||
private name: string;
|
||||
private description: string;
|
||||
private nameLocalizations: Record<string, string>;
|
||||
private descriptionLocalizations: Record<string, string>;
|
||||
constructor(optionjson: commandOptionJson) {
|
||||
this.required = optionjson.required || false;
|
||||
this.name = optionjson.name;
|
||||
this.nameLocalizations = optionjson.name_localizations || {};
|
||||
this.description = optionjson.description;
|
||||
this.descriptionLocalizations = optionjson.description_localizations || {};
|
||||
this.type = optionjson.type;
|
||||
}
|
||||
match(str: string) {
|
||||
return str === this.name;
|
||||
}
|
||||
get localizedName() {
|
||||
return this.nameLocalizations[I18n.lang] || this.name;
|
||||
}
|
||||
get localizedDescription() {
|
||||
return this.descriptionLocalizations[I18n.lang] || this.description;
|
||||
}
|
||||
static toOption(optionjson: commandOptionJson, owner: Command): Option {
|
||||
switch (optionjson.type) {
|
||||
case 3:
|
||||
return new StringOption(optionjson, owner);
|
||||
default:
|
||||
return new ErrorOption(optionjson);
|
||||
}
|
||||
}
|
||||
abstract toHTML(state: string, channel: Channel): HTMLElement;
|
||||
imprintName(html: HTMLElement) {
|
||||
html.setAttribute("commandName", this.name);
|
||||
}
|
||||
similar(search: string) {
|
||||
if (search.length === 0) {
|
||||
return 0.1;
|
||||
}
|
||||
const similar = (str: string) => {
|
||||
if (str.includes(search)) {
|
||||
return search.length / str.length;
|
||||
} else if (str.toLowerCase().includes(search.toLowerCase())) {
|
||||
return str.length / str.length / 1.4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
return Math.max(
|
||||
similar(this.name),
|
||||
similar(this.description),
|
||||
similar(this.localizedDescription),
|
||||
similar(this.localizedName),
|
||||
);
|
||||
}
|
||||
toJson(state: string) {
|
||||
return {
|
||||
value: state,
|
||||
type: this.type,
|
||||
name: this.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
class ErrorOption extends Option {
|
||||
constructor(optionjson: commandOptionJson) {
|
||||
super(optionjson);
|
||||
this.required = false;
|
||||
}
|
||||
toHTML(): HTMLElement {
|
||||
const span = document.createElement("span");
|
||||
this.imprintName(span);
|
||||
span.textContent = "Fermi doesn't impl this yet";
|
||||
return span;
|
||||
}
|
||||
}
|
||||
class StringOption extends Option {
|
||||
minLeng: number;
|
||||
maxLeng: number;
|
||||
choices: commandOptionJson["choices"];
|
||||
autocomplete: boolean;
|
||||
owner: Command;
|
||||
constructor(optionjson: commandOptionJson, owner: Command) {
|
||||
super(optionjson);
|
||||
this.owner = owner;
|
||||
this.minLeng = optionjson.min_length || 0;
|
||||
this.maxLeng = optionjson.min_length || 6000;
|
||||
this.choices = optionjson.choices;
|
||||
this.autocomplete = optionjson.autocomplete || false;
|
||||
}
|
||||
|
||||
toHTML(state: string, channel: Channel): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
div.contentEditable = "false";
|
||||
div.classList.add("flexltr", "commandinput");
|
||||
this.imprintName(div);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = this.localizedName + ":";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.value = state;
|
||||
input.onkeydown = (e) => {
|
||||
if (input.selectionStart === 0 && e.key === "Backspace") {
|
||||
const before = !!div.nextSibling;
|
||||
const sib = div.nextSibling || div.previousSibling;
|
||||
div.remove();
|
||||
focusElm(sib as HTMLElement, before);
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
input.onkeyup = (e) => {
|
||||
if (input.selectionStart === input.value.length && e.key === "ArrowRight") {
|
||||
focusElm(div, false);
|
||||
}
|
||||
this.owner.stateChange(this, channel, input.value);
|
||||
};
|
||||
|
||||
div.append(label, input);
|
||||
return div;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Channel} from "./channel.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {actionRow, button, component, select} from "./jsontypes.js";
|
||||
import {MarkDown} from "./markdown";
|
||||
import {Message} from "./message.js";
|
||||
import {FancySelect} from "./utils/fancySelect.js";
|
||||
import {Channel} from "../channel.js";
|
||||
import {I18n} from "../i18n.js";
|
||||
import {actionRow, button, component, select} from "../jsontypes.js";
|
||||
import {MarkDown} from "../markdown";
|
||||
import {Message} from "../message.js";
|
||||
import {FancySelect} from "../utils/fancySelect.js";
|
||||
abstract class compObj {
|
||||
abstract owner: Components;
|
||||
abstract getHTML(): HTMLElement;
|
||||
@@ -206,6 +206,64 @@ export interface favandfreq {
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface applicationJson {
|
||||
description: string;
|
||||
flags: number;
|
||||
icon: null | string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export interface commandOptionJson {
|
||||
type: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
||||
name: string;
|
||||
name_localized?: string;
|
||||
name_localizations?: null | Record<string, string>;
|
||||
description: string;
|
||||
description_localizations?: null | Record<string, string>;
|
||||
description_localized?: string;
|
||||
choices?:
|
||||
| {
|
||||
name: string;
|
||||
name_localizations?: null | Record<string, string>;
|
||||
name_localized?: string | null;
|
||||
value: string | number;
|
||||
}[]
|
||||
| null;
|
||||
options?: commandJson[];
|
||||
channel_types?: number[];
|
||||
min_value?: number;
|
||||
max_value?: number;
|
||||
min_length?: number;
|
||||
max_length?: number;
|
||||
autocomplete?: boolean;
|
||||
required?: boolean;
|
||||
}
|
||||
export interface commandJson {
|
||||
id: string;
|
||||
type: 1 | 2 | 3 | 4;
|
||||
application_id: string;
|
||||
guild_id?: null | string;
|
||||
name: string;
|
||||
name_localized?: string;
|
||||
name_localizations?: null | {[key: string]: string};
|
||||
description: string;
|
||||
description_localizations?: null | {[key: string]: string};
|
||||
options?: commandOptionJson[];
|
||||
default_member_permissions?: null | string;
|
||||
dm_permission: boolean;
|
||||
permissions?: null | {
|
||||
user?: boolean;
|
||||
roles?: {[key: string]: boolean};
|
||||
channels?: {[key: string]: boolean};
|
||||
};
|
||||
nsfw: boolean;
|
||||
integration_types?: null | number[];
|
||||
global_popularity_rank: number;
|
||||
contexts?: null | number[];
|
||||
version: string;
|
||||
handler: 0 | 1 | 2 | 3; //0 really shouldn't be here, but it's a bug and should be treated like 1.
|
||||
}
|
||||
|
||||
interface readySuplemental {
|
||||
op: 0;
|
||||
t: "READY_SUPPLEMENTAL";
|
||||
|
||||
+59
-32
@@ -3389,50 +3389,53 @@ class Localuser {
|
||||
MDSearchOptions(
|
||||
options: (
|
||||
| [string, string, void | HTMLElement]
|
||||
| [string, string, void | HTMLElement, () => void]
|
||||
| [string, string, void | HTMLElement, () => void | boolean]
|
||||
)[],
|
||||
original: string,
|
||||
div: HTMLDivElement,
|
||||
typebox: MarkDown,
|
||||
typebox?: MarkDown,
|
||||
) {
|
||||
if (!div) return;
|
||||
div.innerHTML = "";
|
||||
let i = 0;
|
||||
const htmloptions: HTMLSpanElement[] = [];
|
||||
for (const thing of options) {
|
||||
for (const [name, replace, elm, func] of options) {
|
||||
if (i == 8) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
const span = document.createElement("span");
|
||||
htmloptions.push(span);
|
||||
if (thing[2]) {
|
||||
span.append(thing[2]);
|
||||
if (elm) {
|
||||
span.append(elm);
|
||||
}
|
||||
|
||||
span.append(thing[0]);
|
||||
span.append(name);
|
||||
span.onclick = (e) => {
|
||||
if (e) {
|
||||
const selection = window.getSelection() as Selection;
|
||||
const box = typebox.box.deref();
|
||||
if (!box) return;
|
||||
if (selection) {
|
||||
const pos = getTextNodeAtPosition(
|
||||
box,
|
||||
original.length -
|
||||
(original.match(this.autofillregex) as RegExpMatchArray)[0].length +
|
||||
thing[1].length,
|
||||
);
|
||||
selection.removeAllRanges();
|
||||
const range = new Range();
|
||||
range.setStart(pos.node, pos.position);
|
||||
selection.addRange(range);
|
||||
if (replace) {
|
||||
const selection = window.getSelection() as Selection;
|
||||
const box = typebox?.box.deref();
|
||||
if (!box) return;
|
||||
if (selection) {
|
||||
const pos = getTextNodeAtPosition(
|
||||
box,
|
||||
original.length -
|
||||
(original.match(this.autofillregex) as RegExpMatchArray)[0].length +
|
||||
replace.length,
|
||||
);
|
||||
selection.removeAllRanges();
|
||||
const range = new Range();
|
||||
range.setStart(pos.node, pos.position);
|
||||
selection.addRange(range);
|
||||
}
|
||||
box.focus();
|
||||
}
|
||||
e.preventDefault();
|
||||
box.focus();
|
||||
}
|
||||
this.MDReplace(thing[1], original, typebox);
|
||||
thing[3]?.();
|
||||
if (!func?.() && typebox) {
|
||||
this.MDReplace(replace, original, typebox);
|
||||
}
|
||||
div.innerHTML = "";
|
||||
remove();
|
||||
};
|
||||
@@ -3508,15 +3511,6 @@ class Localuser {
|
||||
typebox,
|
||||
);
|
||||
}
|
||||
async getUser(id: string) {
|
||||
if (this.userMap.has(id)) {
|
||||
return this.userMap.get(id) as User;
|
||||
}
|
||||
return new User(
|
||||
await (await fetch(this.info.api + "/users/" + id, {headers: this.headers})).json(),
|
||||
this,
|
||||
);
|
||||
}
|
||||
MDFineMentionGen(name: string, original: string, box: HTMLDivElement, typebox: MarkDown) {
|
||||
let members: [Member | Role | User | "@everyone", number][] = [];
|
||||
if (this.lookingguild && name !== "everyone") {
|
||||
@@ -3634,6 +3628,34 @@ class Localuser {
|
||||
});
|
||||
this.MDSearchOptions(map, orginal, box, typebox);
|
||||
}
|
||||
async findCommands(search: string, box: HTMLDivElement, md: MarkDown) {
|
||||
const guild = this.lookingguild;
|
||||
if (!guild) return;
|
||||
const commands = await guild.getCommands();
|
||||
const sorted = commands
|
||||
.map((_) => [_, _.similar(search)] as const)
|
||||
.filter((_) => _[1] !== 0)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
this.MDSearchOptions(
|
||||
sorted.map(([elm]) => {
|
||||
return [
|
||||
`/${elm.localizedName}`,
|
||||
"",
|
||||
undefined,
|
||||
() => {
|
||||
this.channelfocus?.startCommand(elm);
|
||||
return true;
|
||||
},
|
||||
] as const;
|
||||
}),
|
||||
"",
|
||||
box,
|
||||
md,
|
||||
);
|
||||
console.log(sorted, search);
|
||||
}
|
||||
search(box: HTMLDivElement, md: MarkDown, str: string, pre: boolean) {
|
||||
if (!pre) {
|
||||
const match = str.match(this.autofillregex);
|
||||
@@ -3660,6 +3682,11 @@ class Localuser {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const command = str.match(/^\/((\s*[\w\d]+)*)$/);
|
||||
if (command) {
|
||||
const search = command[1];
|
||||
this.findCommands(search, box, md);
|
||||
}
|
||||
}
|
||||
box.innerHTML = "";
|
||||
}
|
||||
|
||||
@@ -973,7 +973,9 @@ class MarkDown {
|
||||
) {
|
||||
this.customBox = [stringToHTML, HTMLToString];
|
||||
}
|
||||
boxEnabled = true;
|
||||
boxupdate(offset = 0, allowLazy = true, computedLength: void | number = undefined) {
|
||||
if (!this.boxEnabled) return;
|
||||
const box = this.box.deref();
|
||||
if (!box) return;
|
||||
let restore: undefined | (() => void);
|
||||
|
||||
@@ -15,8 +15,7 @@ import {I18n} from "./i18n.js";
|
||||
import {Hover} from "./hover.js";
|
||||
import {Dialog} from "./settings.js";
|
||||
import {Sticker} from "./sticker.js";
|
||||
import {Components} from "./compontents.js";
|
||||
|
||||
import {Components} from "./interactions/compontents.js";
|
||||
class Message extends SnowFlake {
|
||||
static contextmenu = new Contextmenu<Message, void>("message menu");
|
||||
stickers!: Sticker[];
|
||||
|
||||
@@ -163,6 +163,22 @@ body {
|
||||
justify-content: space-between;
|
||||
padding: 4px 14px;
|
||||
}
|
||||
.commandinput {
|
||||
display: inline-flex !important;
|
||||
width: fit-content;
|
||||
margin-left: 6px;
|
||||
align-items: center;
|
||||
background: var(--primary-bg);
|
||||
padding-left: 4px;
|
||||
border-radius: 4px;
|
||||
input {
|
||||
padding: 5px !important;
|
||||
margin: 0px !important;
|
||||
margin-left: 5px !important;
|
||||
height: 100%;
|
||||
field-sizing: content;
|
||||
}
|
||||
}
|
||||
.flexltr {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
@@ -1955,6 +1971,12 @@ span.instanceStatus {
|
||||
#realbox {
|
||||
padding: 0 16px 28px 16px;
|
||||
}
|
||||
.commandFront {
|
||||
user-select: none !important;
|
||||
padding: 4px;
|
||||
background: var(--secondary-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
#typebox {
|
||||
margin: -10px 0px;
|
||||
flex-grow: 1;
|
||||
|
||||
Reference in New Issue
Block a user