Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
dfe83427e9
17 changed files with 1231 additions and 604 deletions
|
@ -102,10 +102,6 @@ app.use("/", async (req: Request, res: Response)=>{
|
|||
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
|
||||
return;
|
||||
}
|
||||
if(req.path.endsWith("service.js")){
|
||||
res.send("nope :3");
|
||||
return;
|
||||
}
|
||||
const filePath = path.join(__dirname, "webpage", req.path);
|
||||
try{
|
||||
await fs.access(filePath);
|
||||
|
|
|
@ -61,7 +61,7 @@ class Channel extends SnowFlake{
|
|||
this.readbottom();
|
||||
});
|
||||
|
||||
this.contextmenu.addbutton("Settings[temp]", function(this: Channel){
|
||||
this.contextmenu.addbutton("Settings", function(this: Channel){
|
||||
this.generateSettings();
|
||||
});
|
||||
|
||||
|
@ -76,17 +76,6 @@ class Channel extends SnowFlake{
|
|||
}
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(
|
||||
"Edit channel",
|
||||
function(this: Channel){
|
||||
this.editChannel();
|
||||
},
|
||||
null,
|
||||
function(){
|
||||
return this.isAdmin();
|
||||
}
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(
|
||||
"Make invite",
|
||||
function(this: Channel){
|
||||
|
@ -205,15 +194,33 @@ class Channel extends SnowFlake{
|
|||
generateSettings(){
|
||||
this.sortPerms();
|
||||
const settings = new Settings("Settings for " + this.name);
|
||||
|
||||
const s1 = settings.addButton("roles");
|
||||
|
||||
{
|
||||
const gensettings=settings.addButton("Settings");
|
||||
const form=gensettings.addForm("",()=>{},{
|
||||
fetchURL:this.info.api + "/channels/" + this.id,
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
});
|
||||
form.addTextInput("Name:","name",{initText:this.name});
|
||||
form.addMDInput("Topic:","topic",{initText:this.topic});
|
||||
form.addCheckboxInput("NSFW:","nsfw",{initState:this.nsfw});
|
||||
if(this.type!==4){
|
||||
const options=["voice", "text", "announcement"];
|
||||
form.addSelect("Type:","type",options,{
|
||||
defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string)
|
||||
})
|
||||
}
|
||||
form.addPreprocessor((obj:any)=>{
|
||||
obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string]
|
||||
})
|
||||
}
|
||||
const s1 = settings.addButton("Permisions");
|
||||
s1.options.push(
|
||||
new RoleList(
|
||||
this.permission_overwritesar,
|
||||
this.guild,
|
||||
this.updateRolePermissions.bind(this),
|
||||
true
|
||||
this
|
||||
)
|
||||
);
|
||||
settings.show();
|
||||
|
@ -739,68 +746,6 @@ class Channel extends SnowFlake{
|
|||
}),
|
||||
});
|
||||
}
|
||||
editChannel(){
|
||||
let name = this.name;
|
||||
let topic = this.topic;
|
||||
let nsfw = this.nsfw;
|
||||
const thisid = this.id;
|
||||
const thistype = this.type;
|
||||
const full = new Dialog([
|
||||
"hdiv",
|
||||
[
|
||||
"vdiv",
|
||||
[
|
||||
"textbox",
|
||||
"Channel name:",
|
||||
this.name,
|
||||
function(this: HTMLInputElement){
|
||||
name = this.value;
|
||||
},
|
||||
],
|
||||
[
|
||||
"mdbox",
|
||||
"Channel topic:",
|
||||
this.topic,
|
||||
function(this: HTMLTextAreaElement){
|
||||
topic = this.value;
|
||||
},
|
||||
],
|
||||
[
|
||||
"checkbox",
|
||||
"NSFW Channel",
|
||||
this.nsfw,
|
||||
function(this: HTMLInputElement){
|
||||
nsfw = this.checked;
|
||||
},
|
||||
],
|
||||
[
|
||||
"button",
|
||||
"",
|
||||
"submit",
|
||||
()=>{
|
||||
fetch(this.info.api + "/channels/" + thisid, {
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
type: thistype,
|
||||
topic,
|
||||
bitrate: 64000,
|
||||
user_limit: 0,
|
||||
nsfw,
|
||||
flags: 0,
|
||||
rate_limit_per_user: 0,
|
||||
}),
|
||||
});
|
||||
console.log(full);
|
||||
full.hide();
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
full.show();
|
||||
console.log(full);
|
||||
}
|
||||
deleteChannel(){
|
||||
fetch(this.info.api + "/channels/" + this.id, {
|
||||
method: "DELETE",
|
||||
|
@ -898,7 +843,7 @@ class Channel extends SnowFlake{
|
|||
loading.classList.add("loading");
|
||||
this.rendertyping();
|
||||
this.localuser.getSidePannel();
|
||||
if(this.voice){
|
||||
if(this.voice&&localStorage.getItem("Voice enabled")){
|
||||
this.localuser.joinVoice(this);
|
||||
}
|
||||
await this.putmessages();
|
||||
|
@ -1231,12 +1176,11 @@ class Channel extends SnowFlake{
|
|||
|
||||
this.children = [];
|
||||
this.guild_id = json.guild_id;
|
||||
const oldover=this.permission_overwrites;
|
||||
this.permission_overwrites = new Map();
|
||||
this.permission_overwritesar=[];
|
||||
for(const thing of json.permission_overwrites){
|
||||
if(
|
||||
thing.id === "1182819038095799904" ||
|
||||
thing.id === "1182820803700625444"
|
||||
){
|
||||
if(thing.id === "1182819038095799904" || thing.id === "1182820803700625444"){
|
||||
continue;
|
||||
}
|
||||
this.permission_overwrites.set(
|
||||
|
@ -1251,9 +1195,26 @@ class Channel extends SnowFlake{
|
|||
}
|
||||
}
|
||||
}
|
||||
const nchange=[...new Set<string>().union(oldover).difference(this.permission_overwrites)];
|
||||
const pchange=[...new Set<string>().union(this.permission_overwrites).difference(oldover)];
|
||||
for(const thing of nchange){
|
||||
const role=this.guild.roleids.get(thing);
|
||||
if(role){
|
||||
this.croleUpdate(role,new Permissions("0"),false)
|
||||
}
|
||||
}
|
||||
for(const thing of pchange){
|
||||
const role=this.guild.roleids.get(thing);
|
||||
const perms=this.permission_overwrites.get(thing);
|
||||
if(role&&perms){
|
||||
this.croleUpdate(role,perms,true);
|
||||
}
|
||||
}
|
||||
console.log(pchange,nchange);
|
||||
this.topic = json.topic;
|
||||
this.nsfw = json.nsfw;
|
||||
}
|
||||
croleUpdate:(role:Role,perm:Permissions,added:boolean)=>unknown=()=>{};
|
||||
typingstart(){
|
||||
if(this.typing > Date.now()){
|
||||
return;
|
||||
|
@ -1366,16 +1327,14 @@ class Channel extends SnowFlake{
|
|||
return;
|
||||
}
|
||||
if(
|
||||
this.localuser.lookingguild?.prevchannel === this &&
|
||||
document.hasFocus()
|
||||
this.localuser.lookingguild?.prevchannel === this && document.hasFocus()
|
||||
){
|
||||
return;
|
||||
}
|
||||
if(this.notification === "all"){
|
||||
this.notify(messagez);
|
||||
}else if(
|
||||
this.notification === "mentions" &&
|
||||
messagez.mentionsuser(this.localuser.user)
|
||||
this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)
|
||||
){
|
||||
this.notify(messagez);
|
||||
}
|
||||
|
@ -1445,21 +1404,23 @@ class Channel extends SnowFlake{
|
|||
if(permission){
|
||||
permission.allow = perms.allow;
|
||||
permission.deny = perms.deny;
|
||||
}else{
|
||||
//this.permission_overwrites.set(id,perms);
|
||||
}
|
||||
await fetch(
|
||||
this.info.api + "/channels/" + this.id + "/permissions/" + id,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
allow: permission.allow.toString(),
|
||||
deny: permission.deny.toString(),
|
||||
allow: perms.allow.toString(),
|
||||
deny: perms.deny.toString(),
|
||||
id,
|
||||
type: 0,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Channel.setupcontextmenu();
|
||||
export{ Channel };
|
||||
|
|
|
@ -96,7 +96,7 @@ class Contextmenu<x, y>{
|
|||
event.stopImmediatePropagation();
|
||||
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
|
||||
}
|
||||
});
|
||||
},{passive:true});
|
||||
return func;
|
||||
}
|
||||
static keepOnScreen(obj: HTMLElement){
|
||||
|
|
|
@ -13,6 +13,7 @@ import{
|
|||
emojijson,
|
||||
memberjson,
|
||||
invitejson,
|
||||
rolesjson,
|
||||
}from"./jsontypes.js";
|
||||
import{ User }from"./user.js";
|
||||
|
||||
|
@ -114,16 +115,67 @@ class Guild extends SnowFlake{
|
|||
}
|
||||
form.addTextInput("Region:", "region", { initText: region });
|
||||
}
|
||||
const s1 = settings.addButton("roles");
|
||||
const s1 = settings.addButton("Roles");
|
||||
const permlist: [Role, Permissions][] = [];
|
||||
for(const thing of this.roles){
|
||||
permlist.push([thing, thing.permissions]);
|
||||
}
|
||||
s1.options.push(
|
||||
new RoleList(permlist, this, this.updateRolePermissions.bind(this))
|
||||
new RoleList(permlist, this, this.updateRolePermissions.bind(this),false)
|
||||
);
|
||||
settings.show();
|
||||
}
|
||||
roleUpdate:(role:Role,added:-1|0|1)=>unknown=()=>{};
|
||||
sortRoles(){
|
||||
this.roles.sort((a,b)=>(b.position-a.position));
|
||||
}
|
||||
async recalcRoles(){
|
||||
let position=this.roles.length;
|
||||
const map=this.roles.map(_=>{
|
||||
position--;
|
||||
return {id:_.id,position};
|
||||
})
|
||||
await fetch(this.info.api+"/guilds/"+this.id+"/roles",{
|
||||
method:"PATCH",
|
||||
body:JSON.stringify(map),
|
||||
headers:this.headers
|
||||
})
|
||||
}
|
||||
newRole(rolej:rolesjson){
|
||||
const role=new Role(rolej,this);
|
||||
this.roles.push(role);
|
||||
this.roleids.set(role.id, role);
|
||||
this.sortRoles();
|
||||
this.roleUpdate(role,1);
|
||||
}
|
||||
updateRole(rolej:rolesjson){
|
||||
const role=this.roleids.get(rolej.id) as Role;
|
||||
role.newJson(rolej);
|
||||
this.roleUpdate(role,0);
|
||||
}
|
||||
memberupdate(json:memberjson){
|
||||
let member:undefined|Member=undefined;
|
||||
for(const thing of this.members){
|
||||
if(thing.id===json.id){
|
||||
member=thing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!member) return;
|
||||
member.update(json);
|
||||
if(member===this.member){
|
||||
console.log(member);
|
||||
this.loadGuild();
|
||||
}
|
||||
}
|
||||
deleteRole(id:string){
|
||||
const role = this.roleids.get(id);
|
||||
if(!role) return;
|
||||
this.roleids.delete(id);
|
||||
this.roles.splice(this.roles.indexOf(role),1);
|
||||
this.roleUpdate(role,-1);
|
||||
}
|
||||
constructor(
|
||||
json: guildjson | -1,
|
||||
owner: Localuser,
|
||||
|
@ -153,6 +205,7 @@ class Guild extends SnowFlake{
|
|||
this.roles.push(roleh);
|
||||
this.roleids.set(roleh.id, roleh);
|
||||
}
|
||||
this.sortRoles();
|
||||
if(member instanceof User){
|
||||
Member.resolveMember(member, this).then(_=>{
|
||||
if(_){
|
||||
|
|
|
@ -7,22 +7,22 @@ fetch("/instances.json")
|
|||
.then(
|
||||
(
|
||||
json: {
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: { alltime: number; daytime: number; weektime: number };
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
};
|
||||
}[]
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: { alltime: number; daytime: number; weektime: number };
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
};
|
||||
}[]
|
||||
)=>{
|
||||
console.warn(json);
|
||||
for(const instance of json){
|
||||
|
@ -77,8 +77,7 @@ login?: string;
|
|||
div.append(statbox);
|
||||
div.onclick = _=>{
|
||||
if(instance.online){
|
||||
window.location.href =
|
||||
"/register.html?instance=" + encodeURI(instance.name);
|
||||
window.location.href = "/register.html?instance=" + encodeURI(instance.name);
|
||||
}else{
|
||||
alert("Instance is offline, can't connect");
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
"display": true,
|
||||
"urls": {
|
||||
"wellknown": "https://greysilly7.xyz",
|
||||
"api": "https://api.spacebar.greysilly7.xyz/api",
|
||||
"cdn": "https://cdn.spacebar.greysilly7.xyz",
|
||||
"gateway": "wss://gateway.spacebar.greysilly7.xyz"
|
||||
"api": "https://api-spacebar.greysilly7.xyz/api",
|
||||
"cdn": "https://cdn-spacebar.greysilly7.xyz",
|
||||
"gateway": "wss://gateway-spacebar.greysilly7.xyz"
|
||||
},
|
||||
"contactInfo": {
|
||||
"dicord": "greysilly7",
|
||||
|
|
|
@ -1,120 +1,120 @@
|
|||
type readyjson = {
|
||||
op: 0;
|
||||
t: "READY";
|
||||
s: number;
|
||||
d: {
|
||||
v: number;
|
||||
user: mainuserjson;
|
||||
user_settings: {
|
||||
index: number;
|
||||
afk_timeout: number;
|
||||
allow_accessibility_detection: boolean;
|
||||
animate_emoji: boolean;
|
||||
animate_stickers: number;
|
||||
contact_sync_enabled: boolean;
|
||||
convert_emoticons: boolean;
|
||||
custom_status: string;
|
||||
default_guilds_restricted: boolean;
|
||||
detect_platform_accounts: boolean;
|
||||
developer_mode: boolean;
|
||||
disable_games_tab: boolean;
|
||||
enable_tts_command: boolean;
|
||||
explicit_content_filter: 0;
|
||||
friend_discovery_flags: 0;
|
||||
friend_source_flags: {
|
||||
all: boolean;
|
||||
}; //might be missing things here
|
||||
gateway_connected: boolean;
|
||||
gif_auto_play: boolean;
|
||||
guild_folders: []; //need an example of this not empty
|
||||
guild_positions: []; //need an example of this not empty
|
||||
inline_attachment_media: boolean;
|
||||
inline_embed_media: boolean;
|
||||
locale: string;
|
||||
message_display_compact: boolean;
|
||||
native_phone_integration_enabled: boolean;
|
||||
render_embeds: boolean;
|
||||
render_reactions: boolean;
|
||||
restricted_guilds: []; //need an example of this not empty
|
||||
show_current_game: boolean;
|
||||
status: string;
|
||||
stream_notifications_enabled: boolean;
|
||||
theme: string;
|
||||
timezone_offset: number;
|
||||
view_nsfw_guilds: boolean;
|
||||
};
|
||||
guilds: guildjson[];
|
||||
relationships: {
|
||||
id: string;
|
||||
type: 0 | 1 | 2 | 3 | 4;
|
||||
nickname: string | null;
|
||||
user: userjson;
|
||||
}[];
|
||||
read_state: {
|
||||
entries: {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
last_message_id: string;
|
||||
last_pin_timestamp: string;
|
||||
mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware
|
||||
}[];
|
||||
partial: boolean;
|
||||
version: number;
|
||||
};
|
||||
user_guild_settings: {
|
||||
entries: {
|
||||
channel_overrides: unknown[]; //will have to find example
|
||||
message_notifications: number;
|
||||
flags: number;
|
||||
hide_muted_channels: boolean;
|
||||
mobile_push: boolean;
|
||||
mute_config: null;
|
||||
mute_scheduled_events: boolean;
|
||||
muted: boolean;
|
||||
notify_highlights: number;
|
||||
suppress_everyone: boolean;
|
||||
suppress_roles: boolean;
|
||||
version: number;
|
||||
guild_id: string;
|
||||
}[];
|
||||
partial: boolean;
|
||||
version: number;
|
||||
};
|
||||
private_channels: dirrectjson[];
|
||||
session_id: string;
|
||||
country_code: string;
|
||||
users: userjson[];
|
||||
merged_members: [memberjson][];
|
||||
sessions: {
|
||||
active: boolean;
|
||||
activities: []; //will need to find example of this
|
||||
client_info: {
|
||||
version: number;
|
||||
};
|
||||
session_id: string;
|
||||
status: string;
|
||||
}[];
|
||||
resume_gateway_url: string;
|
||||
consents: {
|
||||
personalization: {
|
||||
consented: boolean;
|
||||
};
|
||||
};
|
||||
experiments: []; //not sure if I need to do this :P
|
||||
guild_join_requests: []; //need to get examples
|
||||
connected_accounts: []; //need to get examples
|
||||
guild_experiments: []; //need to get examples
|
||||
geo_ordered_rtc_regions: []; //need to get examples
|
||||
api_code_version: number;
|
||||
friend_suggestion_count: number;
|
||||
analytics_token: string;
|
||||
tutorial: boolean;
|
||||
session_type: string;
|
||||
auth_session_id_hash: string;
|
||||
notification_settings: {
|
||||
flags: number;
|
||||
};
|
||||
};
|
||||
op: 0;
|
||||
t: "READY";
|
||||
s: number;
|
||||
d: {
|
||||
v: number;
|
||||
user: mainuserjson;
|
||||
user_settings: {
|
||||
index: number;
|
||||
afk_timeout: number;
|
||||
allow_accessibility_detection: boolean;
|
||||
animate_emoji: boolean;
|
||||
animate_stickers: number;
|
||||
contact_sync_enabled: boolean;
|
||||
convert_emoticons: boolean;
|
||||
custom_status: string;
|
||||
default_guilds_restricted: boolean;
|
||||
detect_platform_accounts: boolean;
|
||||
developer_mode: boolean;
|
||||
disable_games_tab: boolean;
|
||||
enable_tts_command: boolean;
|
||||
explicit_content_filter: 0;
|
||||
friend_discovery_flags: 0;
|
||||
friend_source_flags: {
|
||||
all: boolean;
|
||||
}; //might be missing things here
|
||||
gateway_connected: boolean;
|
||||
gif_auto_play: boolean;
|
||||
guild_folders: []; //need an example of this not empty
|
||||
guild_positions: []; //need an example of this not empty
|
||||
inline_attachment_media: boolean;
|
||||
inline_embed_media: boolean;
|
||||
locale: string;
|
||||
message_display_compact: boolean;
|
||||
native_phone_integration_enabled: boolean;
|
||||
render_embeds: boolean;
|
||||
render_reactions: boolean;
|
||||
restricted_guilds: []; //need an example of this not empty
|
||||
show_current_game: boolean;
|
||||
status: string;
|
||||
stream_notifications_enabled: boolean;
|
||||
theme: string;
|
||||
timezone_offset: number;
|
||||
view_nsfw_guilds: boolean;
|
||||
};
|
||||
guilds: guildjson[];
|
||||
relationships: {
|
||||
id: string;
|
||||
type: 0 | 1 | 2 | 3 | 4;
|
||||
nickname: string | null;
|
||||
user: userjson;
|
||||
}[];
|
||||
read_state: {
|
||||
entries: {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
last_message_id: string;
|
||||
last_pin_timestamp: string;
|
||||
mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware
|
||||
}[];
|
||||
partial: boolean;
|
||||
version: number;
|
||||
};
|
||||
user_guild_settings: {
|
||||
entries: {
|
||||
channel_overrides: unknown[]; //will have to find example
|
||||
message_notifications: number;
|
||||
flags: number;
|
||||
hide_muted_channels: boolean;
|
||||
mobile_push: boolean;
|
||||
mute_config: null;
|
||||
mute_scheduled_events: boolean;
|
||||
muted: boolean;
|
||||
notify_highlights: number;
|
||||
suppress_everyone: boolean;
|
||||
suppress_roles: boolean;
|
||||
version: number;
|
||||
guild_id: string;
|
||||
}[];
|
||||
partial: boolean;
|
||||
version: number;
|
||||
};
|
||||
private_channels: dirrectjson[];
|
||||
session_id: string;
|
||||
country_code: string;
|
||||
users: userjson[];
|
||||
merged_members: [memberjson][];
|
||||
sessions: {
|
||||
active: boolean;
|
||||
activities: []; //will need to find example of this
|
||||
client_info: {
|
||||
version: number;
|
||||
};
|
||||
session_id: string;
|
||||
status: string;
|
||||
}[];
|
||||
resume_gateway_url: string;
|
||||
consents: {
|
||||
personalization: {
|
||||
consented: boolean;
|
||||
};
|
||||
};
|
||||
experiments: []; //not sure if I need to do this :P
|
||||
guild_join_requests: []; //need to get examples
|
||||
connected_accounts: []; //need to get examples
|
||||
guild_experiments: []; //need to get examples
|
||||
geo_ordered_rtc_regions: []; //need to get examples
|
||||
api_code_version: number;
|
||||
friend_suggestion_count: number;
|
||||
analytics_token: string;
|
||||
tutorial: boolean;
|
||||
session_type: string;
|
||||
auth_session_id_hash: string;
|
||||
notification_settings: {
|
||||
flags: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
type mainuserjson = userjson & {
|
||||
flags: number;
|
||||
|
@ -129,20 +129,20 @@ type mainuserjson = userjson & {
|
|||
disabled: boolean;
|
||||
};
|
||||
type userjson = {
|
||||
username: string;
|
||||
discriminator: string;
|
||||
id: string;
|
||||
public_flags: number;
|
||||
avatar: string | null;
|
||||
accent_color: number;
|
||||
banner?: string;
|
||||
bio: string;
|
||||
bot: boolean;
|
||||
premium_since: string;
|
||||
premium_type: number;
|
||||
theme_colors: string;
|
||||
pronouns: string;
|
||||
badge_ids: string[];
|
||||
username: string;
|
||||
discriminator: string;
|
||||
id: string;
|
||||
public_flags: number;
|
||||
avatar: string | null;
|
||||
accent_color: number;
|
||||
banner?: string;
|
||||
bio: string;
|
||||
bot: boolean;
|
||||
premium_since: string;
|
||||
premium_type: number;
|
||||
theme_colors: string;
|
||||
pronouns: string;
|
||||
badge_ids: string[];
|
||||
};
|
||||
type memberjson = {
|
||||
index?: number;
|
||||
|
@ -163,9 +163,9 @@ type memberjson = {
|
|||
last_message_id?: boolean; //What???
|
||||
};
|
||||
type emojijson = {
|
||||
name: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
name: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
};
|
||||
|
||||
type guildjson = {
|
||||
|
@ -225,37 +225,37 @@ type guildjson = {
|
|||
joined_at: string;
|
||||
};
|
||||
type startTypingjson = {
|
||||
d: {
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
user_id: string;
|
||||
timestamp: number;
|
||||
member?: memberjson;
|
||||
};
|
||||
d: {
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
user_id: string;
|
||||
timestamp: number;
|
||||
member?: memberjson;
|
||||
};
|
||||
};
|
||||
type channeljson = {
|
||||
id: string;
|
||||
created_at: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
type: number;
|
||||
last_message_id: string;
|
||||
guild_id: string;
|
||||
parent_id: string;
|
||||
last_pin_timestamp: string;
|
||||
default_auto_archive_duration: number;
|
||||
permission_overwrites: {
|
||||
id: string;
|
||||
allow: string;
|
||||
deny: string;
|
||||
}[];
|
||||
video_quality_mode: null;
|
||||
nsfw: boolean;
|
||||
topic: string;
|
||||
retention_policy_id: string;
|
||||
flags: number;
|
||||
default_thread_rate_limit_per_user: number;
|
||||
position: number;
|
||||
id: string;
|
||||
created_at: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
type: number;
|
||||
last_message_id: string;
|
||||
guild_id: string;
|
||||
parent_id: string;
|
||||
last_pin_timestamp: string;
|
||||
default_auto_archive_duration: number;
|
||||
permission_overwrites: {
|
||||
id: string;
|
||||
allow: string;
|
||||
deny: string;
|
||||
}[];
|
||||
video_quality_mode: null;
|
||||
nsfw: boolean;
|
||||
topic: string;
|
||||
retention_policy_id: string;
|
||||
flags: number;
|
||||
default_thread_rate_limit_per_user: number;
|
||||
position: number;
|
||||
};
|
||||
type rolesjson = {
|
||||
id: string;
|
||||
|
@ -272,127 +272,136 @@ type rolesjson = {
|
|||
flags: number;
|
||||
};
|
||||
type dirrectjson = {
|
||||
id: string;
|
||||
flags: number;
|
||||
last_message_id: string;
|
||||
type: number;
|
||||
recipients: userjson[];
|
||||
is_spam: boolean;
|
||||
id: string;
|
||||
flags: number;
|
||||
last_message_id: string;
|
||||
type: number;
|
||||
recipients: userjson[];
|
||||
is_spam: boolean;
|
||||
};
|
||||
type messagejson = {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
guild_id: string;
|
||||
author: userjson;
|
||||
member?: memberjson;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
edited_timestamp: string;
|
||||
tts: boolean;
|
||||
mention_everyone: boolean;
|
||||
mentions: []; //need examples to fix
|
||||
mention_roles: []; //need examples to fix
|
||||
attachments: filejson[];
|
||||
embeds: embedjson[];
|
||||
reactions: {
|
||||
count: number;
|
||||
emoji: emojijson; //very likely needs expanding
|
||||
me: boolean;
|
||||
}[];
|
||||
nonce: string;
|
||||
pinned: boolean;
|
||||
type: number;
|
||||
id: string;
|
||||
channel_id: string;
|
||||
guild_id: string;
|
||||
author: userjson;
|
||||
member?: memberjson;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
edited_timestamp: string;
|
||||
tts: boolean;
|
||||
mention_everyone: boolean;
|
||||
mentions: []; //need examples to fix
|
||||
mention_roles: []; //need examples to fix
|
||||
attachments: filejson[];
|
||||
embeds: embedjson[];
|
||||
reactions: {
|
||||
count: number;
|
||||
emoji: emojijson; //very likely needs expanding
|
||||
me: boolean;
|
||||
}[];
|
||||
nonce: string;
|
||||
pinned: boolean;
|
||||
type: number;
|
||||
};
|
||||
type filejson = {
|
||||
id: string;
|
||||
filename: string;
|
||||
content_type: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
proxy_url: string | undefined;
|
||||
url: string;
|
||||
size: number;
|
||||
id: string;
|
||||
filename: string;
|
||||
content_type: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
proxy_url: string | undefined;
|
||||
url: string;
|
||||
size: number;
|
||||
};
|
||||
type embedjson = {
|
||||
type: string | null;
|
||||
color?: number;
|
||||
author: {
|
||||
icon_url?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
title?: string;
|
||||
};
|
||||
title?: string;
|
||||
url?: string;
|
||||
description?: string;
|
||||
fields?: {
|
||||
name: string;
|
||||
value: string;
|
||||
inline: boolean;
|
||||
}[];
|
||||
footer?: {
|
||||
icon_url?: string;
|
||||
text?: string;
|
||||
thumbnail?: string;
|
||||
};
|
||||
timestamp?: string;
|
||||
thumbnail: {
|
||||
proxy_url: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
provider: {
|
||||
name: string;
|
||||
};
|
||||
video?: {
|
||||
url: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
proxy_url?: string;
|
||||
};
|
||||
invite?: {
|
||||
url: string;
|
||||
code: string;
|
||||
};
|
||||
type: string | null;
|
||||
color?: number;
|
||||
author: {
|
||||
icon_url?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
title?: string;
|
||||
};
|
||||
title?: string;
|
||||
url?: string;
|
||||
description?: string;
|
||||
fields?: {
|
||||
name: string;
|
||||
value: string;
|
||||
inline: boolean;
|
||||
}[];
|
||||
footer?: {
|
||||
icon_url?: string;
|
||||
text?: string;
|
||||
thumbnail?: string;
|
||||
};
|
||||
timestamp?: string;
|
||||
thumbnail: {
|
||||
proxy_url: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
provider: {
|
||||
name: string;
|
||||
};
|
||||
video?: {
|
||||
url: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
proxy_url?: string;
|
||||
};
|
||||
invite?: {
|
||||
url: string;
|
||||
code: string;
|
||||
};
|
||||
};
|
||||
type invitejson = {
|
||||
code: string;
|
||||
temporary: boolean;
|
||||
uses: number;
|
||||
max_use: number;
|
||||
max_age: number;
|
||||
created_at: string;
|
||||
expires_at: string;
|
||||
guild_id: string;
|
||||
channel_id: string;
|
||||
inviter_id: string;
|
||||
target_user_id: string | null;
|
||||
target_user_type: string | null;
|
||||
vanity_url: string | null;
|
||||
flags: number;
|
||||
guild: guildjson["properties"];
|
||||
channel: channeljson;
|
||||
inviter: userjson;
|
||||
code: string;
|
||||
temporary: boolean;
|
||||
uses: number;
|
||||
max_use: number;
|
||||
max_age: number;
|
||||
created_at: string;
|
||||
expires_at: string;
|
||||
guild_id: string;
|
||||
channel_id: string;
|
||||
inviter_id: string;
|
||||
target_user_id: string | null;
|
||||
target_user_type: string | null;
|
||||
vanity_url: string | null;
|
||||
flags: number;
|
||||
guild: guildjson["properties"];
|
||||
channel: channeljson;
|
||||
inviter: userjson;
|
||||
};
|
||||
type presencejson = {
|
||||
status: string;
|
||||
since: number | null;
|
||||
activities: any[]; //bit more complicated but not now
|
||||
afk: boolean;
|
||||
user?: userjson;
|
||||
status: string;
|
||||
since: number | null;
|
||||
activities: any[]; //bit more complicated but not now
|
||||
afk: boolean;
|
||||
user?: userjson;
|
||||
};
|
||||
type messageCreateJson = {
|
||||
op: 0;
|
||||
d: {
|
||||
guild_id?: string;
|
||||
channel_id?: string;
|
||||
} & messagejson;
|
||||
s: number;
|
||||
t: "MESSAGE_CREATE";
|
||||
op: 0;
|
||||
d: {
|
||||
guild_id?: string;
|
||||
channel_id?: string;
|
||||
} & messagejson;
|
||||
s: number;
|
||||
t: "MESSAGE_CREATE";
|
||||
};
|
||||
type roleCreate={
|
||||
op: 0,
|
||||
t: "GUILD_ROLE_CREATE",
|
||||
d: {
|
||||
guild_id: string,
|
||||
role: rolesjson
|
||||
},
|
||||
s: 6
|
||||
}
|
||||
type wsjson =
|
||||
| {
|
||||
roleCreate | {
|
||||
op: 0;
|
||||
d: any;
|
||||
s: number;
|
||||
|
@ -469,7 +478,28 @@ type wsjson =
|
|||
guild_id: string;
|
||||
emoji: emojijson;
|
||||
};
|
||||
s: 3;
|
||||
s: number;
|
||||
}|{
|
||||
op: 0,
|
||||
t: "GUILD_ROLE_UPDATE",
|
||||
d: {
|
||||
guild_id: string,
|
||||
role: rolesjson
|
||||
},
|
||||
"s": number
|
||||
}|{
|
||||
op: 0,
|
||||
t: "GUILD_ROLE_DELETE",
|
||||
d: {
|
||||
guild_id: string,
|
||||
role_id: string
|
||||
},
|
||||
s:number
|
||||
}|{
|
||||
op: 0,
|
||||
t: "GUILD_MEMBER_UPDATE",
|
||||
d: memberjson,
|
||||
"s": 3
|
||||
}|memberlistupdatejson|voiceupdate|voiceserverupdate;
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import{ Direct }from"./direct.js";
|
|||
import{ AVoice }from"./audio.js";
|
||||
import{ User }from"./user.js";
|
||||
import{ Dialog }from"./dialog.js";
|
||||
import{ getapiurls, getBulkInfo, setTheme, Specialuser }from"./login.js";
|
||||
import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js";
|
||||
import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js";
|
||||
import{ Member }from"./member.js";
|
||||
import{ Form, FormError, Options, Settings }from"./settings.js";
|
||||
|
@ -80,6 +80,7 @@ class Localuser{
|
|||
this.handleVoice();
|
||||
this.mfa_enabled = ready.d.user.mfa_enabled as boolean;
|
||||
this.userinfo.username = this.user.username;
|
||||
this.userinfo.id = this.user.id;
|
||||
this.userinfo.pfpsrc = this.user.getpfpsrc();
|
||||
this.status = this.ready.d.user_settings.status;
|
||||
this.channelfocus = undefined;
|
||||
|
@ -494,7 +495,32 @@ class Localuser{
|
|||
this.voiceFactory.voiceServerUpdate(temp)
|
||||
}
|
||||
break;
|
||||
case "GUILD_ROLE_CREATE":{
|
||||
const guild=this.guildids.get(temp.d.guild_id);
|
||||
if(!guild) break;
|
||||
guild.newRole(temp.d.role);
|
||||
break;
|
||||
}
|
||||
case "GUILD_ROLE_UPDATE":{
|
||||
const guild=this.guildids.get(temp.d.guild_id);
|
||||
if(!guild) break;
|
||||
guild.updateRole(temp.d.role);
|
||||
break;
|
||||
}
|
||||
case "GUILD_ROLE_DELETE":{
|
||||
const guild=this.guildids.get(temp.d.guild_id);
|
||||
if(!guild) break;
|
||||
guild.deleteRole(temp.d.role_id);
|
||||
break;
|
||||
}
|
||||
case "GUILD_MEMBER_UPDATE":{
|
||||
const guild=this.guildids.get(temp.d.guild_id);
|
||||
if(!guild) break;
|
||||
guild.memberupdate(temp.d)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}else if(temp.op === 10){
|
||||
|
@ -1229,6 +1255,40 @@ class Localuser{
|
|||
{ initColor: userinfos.accent_color }
|
||||
);
|
||||
}
|
||||
{
|
||||
const box=tas.addCheckboxInput("Enable experimental Voice support",()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))});
|
||||
box.onchange=(e)=>{
|
||||
if(e){
|
||||
if(confirm("Are you sure you want to enable this, this is very experimental and is likely to cause issues. (this feature is for devs, please don't enable if you don't know what you're doing)")){
|
||||
localStorage.setItem("Voice enabled","true")
|
||||
|
||||
}else{
|
||||
box.value=true;
|
||||
const checkbox=box.input.deref();
|
||||
if(checkbox){
|
||||
checkbox.checked=false;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
localStorage.removeItem("Voice enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const update=settings.addButton("Update settings")
|
||||
const sw=update.addSelect("Service Worker setting",()=>{},["False","Offline only","True"],{
|
||||
defaultIndex:["false","offlineOnly","true"].indexOf(localStorage.getItem("SWMode") as string)
|
||||
});
|
||||
sw.onchange=(e)=>{
|
||||
SW.setMode(["false","offlineOnly","true"][e] as "false"|"offlineOnly"|"true")
|
||||
}
|
||||
update.addButtonInput("","Check for update",()=>{
|
||||
SW.checkUpdate();
|
||||
});
|
||||
update.addButtonInput("","Clear cache",()=>{
|
||||
SW.forceClear();
|
||||
});
|
||||
}
|
||||
{
|
||||
const security = settings.addButton("Account Settings");
|
||||
|
|
|
@ -47,7 +47,7 @@ function trimswitcher(){
|
|||
if(wellknown.at(-1) !== "/"){
|
||||
wellknown += "/";
|
||||
}
|
||||
wellknown += user.username;
|
||||
wellknown =(user.id||user.email)+"@"+wellknown;
|
||||
if(map.has(wellknown)){
|
||||
const otheruser = map.get(wellknown);
|
||||
if(otheruser[1].serverurls.wellknown.at(-1) === "/"){
|
||||
|
@ -178,6 +178,13 @@ class Specialuser{
|
|||
get localuserStore(){
|
||||
return this.json.localuserStore;
|
||||
}
|
||||
set id(e){
|
||||
this.json.id = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get id(){
|
||||
return this.json.id;
|
||||
}
|
||||
get uid(){
|
||||
return this.email + this.serverurls.wellknown;
|
||||
}
|
||||
|
@ -532,12 +539,34 @@ if(document.getElementById("form")){
|
|||
}
|
||||
}
|
||||
//this currently does not work, and need to be implemented better at some time.
|
||||
/*
|
||||
if ("serviceWorker" in navigator){
|
||||
if(!localStorage.getItem("SWMode")){
|
||||
localStorage.setItem("SWMode","true");
|
||||
}
|
||||
class SW{
|
||||
static worker:undefined|ServiceWorker;
|
||||
static setMode(mode:"false"|"offlineOnly"|"true"){
|
||||
localStorage.setItem("SWMode",mode);
|
||||
if(this.worker){
|
||||
this.worker.postMessage({data:mode,code:"setMode"});
|
||||
}
|
||||
}
|
||||
static checkUpdate(){
|
||||
if(this.worker){
|
||||
this.worker.postMessage({code:"CheckUpdate"});
|
||||
}
|
||||
}
|
||||
static forceClear(){
|
||||
if(this.worker){
|
||||
this.worker.postMessage({code:"ForceClear"});
|
||||
}
|
||||
}
|
||||
}
|
||||
export {SW};
|
||||
if ("serviceWorker" in navigator){
|
||||
navigator.serviceWorker.register("/service.js", {
|
||||
scope: "/",
|
||||
}).then((registration) => {
|
||||
let serviceWorker:ServiceWorker;
|
||||
let serviceWorker:ServiceWorker|undefined;
|
||||
if (registration.installing) {
|
||||
serviceWorker = registration.installing;
|
||||
console.log("installing");
|
||||
|
@ -548,15 +577,17 @@ if(document.getElementById("form")){
|
|||
serviceWorker = registration.active;
|
||||
console.log("active");
|
||||
}
|
||||
SW.worker=serviceWorker;
|
||||
SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true");
|
||||
if (serviceWorker) {
|
||||
console.log(serviceWorker.state);
|
||||
serviceWorker.addEventListener("statechange", (e) => {
|
||||
serviceWorker.addEventListener("statechange", (_) => {
|
||||
console.log(serviceWorker.state);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
const switchurl = document.getElementById("switch") as HTMLAreaElement;
|
||||
if(switchurl){
|
||||
switchurl.href += window.location.search;
|
||||
|
|
|
@ -49,6 +49,32 @@ class Member extends SnowFlake{
|
|||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||
});
|
||||
}
|
||||
update(memberjson: memberjson){
|
||||
this.roles=[];
|
||||
for(const key of Object.keys(memberjson)){
|
||||
if(key === "guild" || key === "owner" || key === "user"){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key === "roles"){
|
||||
for(const strrole of memberjson.roles){
|
||||
const role = this.guild.roleids.get(strrole);
|
||||
if(!role)continue;
|
||||
this.roles.push(role);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(key === "presence"){
|
||||
this.getPresence(memberjson.presence);
|
||||
continue;
|
||||
}
|
||||
(this as any)[key] = (memberjson as any)[key];
|
||||
}
|
||||
|
||||
this.roles.sort((a, b)=>{
|
||||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||
});
|
||||
}
|
||||
get guild(){
|
||||
return this.owner;
|
||||
}
|
||||
|
@ -241,6 +267,24 @@ class Member extends SnowFlake{
|
|||
]);
|
||||
menu.show();
|
||||
}
|
||||
addRole(role:Role){
|
||||
const roles=this.roles.map(_=>_.id)
|
||||
roles.push(role.id);
|
||||
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||
method:"PATCH",
|
||||
headers:this.guild.headers,
|
||||
body:JSON.stringify({roles})
|
||||
})
|
||||
}
|
||||
removeRole(role:Role){
|
||||
let roles=this.roles.map(_=>_.id)
|
||||
roles=roles.filter(_=>_!==role.id);
|
||||
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||
method:"PATCH",
|
||||
headers:this.guild.headers,
|
||||
body:JSON.stringify({roles})
|
||||
})
|
||||
}
|
||||
banAPI(reason: string){
|
||||
const headers = structuredClone(this.guild.headers);
|
||||
(headers as any)["x-audit-log-reason"] = reason;
|
||||
|
|
|
@ -3,6 +3,7 @@ import{ Localuser }from"./localuser.js";
|
|||
import{ Guild }from"./guild.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ rolesjson }from"./jsontypes.js";
|
||||
import{ Search }from"./search.js";
|
||||
class Role extends SnowFlake{
|
||||
permissions: Permissions;
|
||||
owner: Guild;
|
||||
|
@ -13,6 +14,7 @@ class Role extends SnowFlake{
|
|||
icon!: string;
|
||||
mentionable!: boolean;
|
||||
unicode_emoji!: string;
|
||||
position!:number;
|
||||
headers: Guild["headers"];
|
||||
constructor(json: rolesjson, owner: Guild){
|
||||
super(json.id);
|
||||
|
@ -27,6 +29,15 @@ class Role extends SnowFlake{
|
|||
this.permissions = new Permissions(json.permissions);
|
||||
this.owner = owner;
|
||||
}
|
||||
newJson(json: rolesjson){
|
||||
for(const thing of Object.keys(json)){
|
||||
if(thing === "id"||thing==="permissions"){
|
||||
continue;
|
||||
}
|
||||
(this as any)[thing] = (json as any)[thing];
|
||||
}
|
||||
this.permissions.allow=BigInt(json.permissions);
|
||||
}
|
||||
get guild(): Guild{
|
||||
return this.owner;
|
||||
}
|
||||
|
@ -39,6 +50,14 @@ class Role extends SnowFlake{
|
|||
}
|
||||
return`#${this.color.toString(16)}`;
|
||||
}
|
||||
canManage(){
|
||||
if(this.guild.member.hasPermission("MANAGE_ROLES")){
|
||||
let max=-Infinity;
|
||||
this.guild.member.roles.forEach(r=>max=Math.max(max,r.position))
|
||||
return this.position<=max||this.guild.properties.owner_id===this.guild.member.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export{ Role };
|
||||
import{ Options }from"./settings.js";
|
||||
|
@ -121,22 +140,25 @@ class PermissionToggle implements OptionsElement<number>{
|
|||
submit(){}
|
||||
}
|
||||
import{ OptionsElement, Buttons }from"./settings.js";
|
||||
import { Contextmenu } from "./contextmenu.js";
|
||||
import { Channel } from "./channel.js";
|
||||
class RoleList extends Buttons{
|
||||
readonly permissions: [Role, Permissions][];
|
||||
permissions: [Role, Permissions][];
|
||||
permission: Permissions;
|
||||
readonly guild: Guild;
|
||||
readonly channel: boolean;
|
||||
declare readonly buttons: [string, string][];
|
||||
readonly channel: false|Channel;
|
||||
declare buttons: [string, string][];
|
||||
readonly options: Options;
|
||||
onchange: Function;
|
||||
curid!: string;
|
||||
constructor(
|
||||
permissions: [Role, Permissions][],
|
||||
guild: Guild,
|
||||
onchange: Function,
|
||||
channel = false
|
||||
){
|
||||
super("Roles");
|
||||
curid?: string;
|
||||
get info(){
|
||||
return this.guild.info;
|
||||
}
|
||||
get headers(){
|
||||
return this.guild.headers;
|
||||
}
|
||||
constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){
|
||||
super("");
|
||||
this.guild = guild;
|
||||
this.permissions = permissions;
|
||||
this.channel = channel;
|
||||
|
@ -147,16 +169,238 @@ class RoleList extends Buttons{
|
|||
}else{
|
||||
this.permission = new Permissions("0");
|
||||
}
|
||||
this.makeguildmenus(options);
|
||||
for(const thing of Permissions.info){
|
||||
options.options.push(
|
||||
new PermissionToggle(thing, this.permission, options)
|
||||
);
|
||||
}
|
||||
for(const i of permissions){
|
||||
console.log(i);
|
||||
this.buttons.push([i[0].name, i[0].id]);
|
||||
}
|
||||
this.options = options;
|
||||
guild.roleUpdate=this.groleUpdate.bind(this);
|
||||
if(channel){
|
||||
channel.croleUpdate=this.croleUpdate.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,perm:Permissions,added:boolean){
|
||||
if(added){
|
||||
this.permissions.push([role,perm])
|
||||
}else{
|
||||
this.permissions=this.permissions.filter(r=>r[0]!==role);
|
||||
}
|
||||
this.redoButtons();
|
||||
}
|
||||
makeguildmenus(option:Options){
|
||||
option.addButtonInput("","Display settings",()=>{
|
||||
const role=this.guild.roleids.get(this.curid as string);
|
||||
if(!role) return;
|
||||
const form=option.addSubForm("Display settings",()=>{},{
|
||||
fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid,
|
||||
method:"PATCH",
|
||||
headers:this.headers,
|
||||
traditionalSubmit:true
|
||||
});
|
||||
form.addTextInput("Role Name:","name",{
|
||||
initText:role.name
|
||||
});
|
||||
form.addCheckboxInput("Hoisted:","hoist",{
|
||||
initState:role.hoist
|
||||
});
|
||||
form.addCheckboxInput("Allow anyone to ping this role:","mentionable",{
|
||||
initState:role.mentionable
|
||||
});
|
||||
const color="#"+role.color.toString(16).padStart(6,"0");
|
||||
form.addColorInput("Color","color",{
|
||||
initColor:color
|
||||
});
|
||||
form.addPreprocessor((obj:any)=>{
|
||||
obj.color=Number("0x"+obj.color.substring(1));
|
||||
console.log(obj.color);
|
||||
})
|
||||
})
|
||||
}
|
||||
static channelrolemenu=this.ChannelRoleMenu();
|
||||
static guildrolemenu=this.GuildRoleMenu();
|
||||
private static ChannelRoleMenu(){
|
||||
const menu=new Contextmenu<RoleList,Role>("role settings");
|
||||
menu.addbutton("Remove role",function(role){
|
||||
if(!this.channel) return;
|
||||
console.log(role);
|
||||
fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{
|
||||
method:"DELETE",
|
||||
headers:this.headers
|
||||
})
|
||||
},null);
|
||||
return menu;
|
||||
}
|
||||
private static GuildRoleMenu(){
|
||||
const menu=new Contextmenu<RoleList,Role>("role settings");
|
||||
menu.addbutton("Delete Role",function(role){
|
||||
if(!confirm("Are you sure you want to delete "+role.name+"?")) return;
|
||||
console.log(role);
|
||||
fetch(this.info.api+"/guilds/"+this.guild.id+"/roles/"+role.id,{
|
||||
method:"DELETE",
|
||||
headers:this.headers
|
||||
})
|
||||
},null);
|
||||
return menu;
|
||||
}
|
||||
redoButtons(){
|
||||
this.buttons=[];
|
||||
this.permissions.sort(([a],[b])=>b.position-a.position);
|
||||
for(const i of this.permissions){
|
||||
this.buttons.push([i[0].name, i[0].id]);
|
||||
}
|
||||
console.log("in here :P")
|
||||
if(!this.buttonList)return;
|
||||
console.log("in here :P");
|
||||
const elms=Array.from(this.buttonList.children);
|
||||
const div=elms[0] as HTMLDivElement;
|
||||
const div2=elms[1] as HTMLDivElement;
|
||||
console.log(div);
|
||||
div.innerHTML="";
|
||||
div.append(this.buttonListGen(div2));//not actually sure why the html is needed
|
||||
}
|
||||
buttonMap=new WeakMap<HTMLButtonElement,Role>();
|
||||
dragged?:HTMLButtonElement;
|
||||
buttonDragEvents(button:HTMLButtonElement,role:Role){
|
||||
this.buttonMap.set(button,role);
|
||||
button.addEventListener("dragstart", e=>{
|
||||
this.dragged = button;
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
button.addEventListener("dragend", ()=>{
|
||||
this.dragged = undefined;
|
||||
});
|
||||
|
||||
button.addEventListener("dragenter", event=>{
|
||||
console.log("enter");
|
||||
event.preventDefault();
|
||||
return true;
|
||||
});
|
||||
|
||||
button.addEventListener("dragover", event=>{
|
||||
event.preventDefault();
|
||||
return true;
|
||||
});
|
||||
|
||||
button.addEventListener("drop", _=>{
|
||||
const role2=this.buttonMap.get(this.dragged as HTMLButtonElement);
|
||||
if(!role2) return;
|
||||
const index2=this.guild.roles.indexOf(role2);
|
||||
this.guild.roles.splice(index2,1);
|
||||
const index=this.guild.roles.indexOf(role);
|
||||
this.guild.roles.splice(index+1,0,role2);
|
||||
this.guild.recalcRoles();
|
||||
console.log(role);
|
||||
});
|
||||
}
|
||||
buttonListGen(html:HTMLElement){
|
||||
const buttonTable=document.createElement("div");
|
||||
buttonTable.classList.add("flexttb");
|
||||
|
||||
const roleRow=document.createElement("div");
|
||||
roleRow.classList.add("flexltr");
|
||||
roleRow.append("Roles");
|
||||
const add=document.createElement("span");
|
||||
add.classList.add("svg-plus","svgicon","addrole");
|
||||
add.onclick=async (e)=>{
|
||||
const box=add.getBoundingClientRect();
|
||||
e.stopPropagation();
|
||||
if(this.channel){
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of this.guild.roles){
|
||||
if(this.permissions.find(r=>r[0]==role)){
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
|
||||
const found=await search.find(box.left,box.top);
|
||||
|
||||
|
||||
if(!found) return;
|
||||
console.log(found);
|
||||
this.onchange(found.id,new Permissions("0","0"));
|
||||
}else{
|
||||
const bar=document.createElement("input");
|
||||
bar.classList.add("fixedsearch");
|
||||
bar.style.left=(box.left^0)+"px";
|
||||
bar.style.top=(box.top^0)+"px";
|
||||
document.body.append(bar);
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu=bar;
|
||||
Contextmenu.keepOnScreen(bar);
|
||||
bar.onchange=()=>{
|
||||
bar.remove();
|
||||
console.log(bar.value)
|
||||
if(bar.value==="") return;
|
||||
fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{
|
||||
method:"POST",
|
||||
headers:this.headers,
|
||||
body:JSON.stringify({
|
||||
color:0,
|
||||
name:bar.value,
|
||||
permissions:""
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
roleRow.append(add);
|
||||
|
||||
buttonTable.append(roleRow);
|
||||
for(const thing of this.buttons){
|
||||
const button = document.createElement("button");
|
||||
button.classList.add("SettingsButton");
|
||||
button.textContent = thing[0];
|
||||
const role=this.guild.roleids.get(thing[1]);
|
||||
if(role){
|
||||
if(!this.channel){
|
||||
if(role.canManage()){
|
||||
this.buttonDragEvents(button,role);
|
||||
button.draggable=true;
|
||||
RoleList.guildrolemenu.bindContextmenu(button,this,role)
|
||||
}
|
||||
}else{
|
||||
if(role.canManage()){
|
||||
RoleList.channelrolemenu.bindContextmenu(button,this,role)
|
||||
}
|
||||
}
|
||||
}
|
||||
button.onclick = _=>{
|
||||
this.generateHTMLArea(thing[1], html);
|
||||
if(this.warndiv){
|
||||
this.warndiv.remove();
|
||||
}
|
||||
};
|
||||
buttonTable.append(button);
|
||||
}
|
||||
return buttonTable;
|
||||
}
|
||||
|
||||
generateButtons(html:HTMLElement):HTMLDivElement{
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("settingbuttons");
|
||||
div.append(this.buttonListGen(html));
|
||||
return div;
|
||||
}
|
||||
handleString(str: string): HTMLElement{
|
||||
this.curid = str;
|
||||
|
|
72
src/webpage/search.ts
Normal file
72
src/webpage/search.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { Contextmenu } from "./contextmenu.js";
|
||||
|
||||
class Search<E>{
|
||||
options:Map<string,E>;
|
||||
readonly keys:string[];
|
||||
constructor(options:[E,string[]][]){
|
||||
const map=options.flatMap(e=>{
|
||||
const val=e[1].map(f=>[f,e[0]]);
|
||||
return val as [string,E][];
|
||||
})
|
||||
this.options=new Map(map);
|
||||
this.keys=[...this.options.keys()];
|
||||
}
|
||||
generateList(str:string,max:number,res:(e:E)=>void){
|
||||
str=str.toLowerCase();
|
||||
const options=this.keys.filter(e=>{
|
||||
return e.toLowerCase().includes(str)
|
||||
});
|
||||
const div=document.createElement("div");
|
||||
div.classList.add("OptionList","flexttb");
|
||||
for(const option of options.slice(0, max)){
|
||||
const hoption=document.createElement("span");
|
||||
hoption.textContent=option;
|
||||
hoption.onclick=()=>{
|
||||
if(!this.options.has(option)) return;
|
||||
res(this.options.get(option) as E)
|
||||
}
|
||||
div.append(hoption);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
async find(x:number,y:number,max=4):Promise<E|undefined>{
|
||||
return new Promise<E|undefined>((res)=>{
|
||||
|
||||
const container=document.createElement("div");
|
||||
container.classList.add("fixedsearch");
|
||||
console.log((x^0)+"",(y^0)+"");
|
||||
container.style.left=(x^0)+"px";
|
||||
container.style.top=(y^0)+"px";
|
||||
const remove=container.remove;
|
||||
container.remove=()=>{
|
||||
remove.call(container);
|
||||
res(undefined);
|
||||
}
|
||||
|
||||
function resolve(e:E){
|
||||
res(e);
|
||||
container.remove();
|
||||
}
|
||||
const bar=document.createElement("input");
|
||||
const options=document.createElement("div");
|
||||
const keydown=()=>{
|
||||
const html=this.generateList(bar.value,max,resolve);
|
||||
options.innerHTML="";
|
||||
options.append(html);
|
||||
}
|
||||
bar.oninput=keydown;
|
||||
keydown();
|
||||
bar.type="text";
|
||||
container.append(bar);
|
||||
container.append(options);
|
||||
document.body.append(container);
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu=container;
|
||||
Contextmenu.keepOnScreen(container);
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
export {Search};
|
|
@ -13,13 +13,13 @@ async function putInCache(request: URL | RequestInfo, response: Response){
|
|||
console.error(error);
|
||||
}
|
||||
}
|
||||
console.log("test");
|
||||
|
||||
let lastcache: string;
|
||||
self.addEventListener("activate", async ()=>{
|
||||
console.log("test2");
|
||||
console.log("Service Worker activated");
|
||||
checkCache();
|
||||
});
|
||||
|
||||
async function checkCache(){
|
||||
if(checkedrecently){
|
||||
return;
|
||||
|
@ -34,7 +34,7 @@ async function checkCache(){
|
|||
console.log(text, lastcache);
|
||||
if(lastcache !== text){
|
||||
deleteoldcache();
|
||||
putInCache("/getupdates", data.clone());
|
||||
putInCache("/getupdates", data);
|
||||
}
|
||||
checkedrecently = true;
|
||||
setTimeout((_: any)=>{
|
||||
|
@ -43,54 +43,99 @@ async function checkCache(){
|
|||
});
|
||||
}
|
||||
var checkedrecently = false;
|
||||
|
||||
function samedomain(url: string | URL){
|
||||
return new URL(url).origin === self.origin;
|
||||
}
|
||||
function isindexhtml(url: string | URL){
|
||||
console.log(url);
|
||||
if(new URL(url).pathname.startsWith("/channels")){
|
||||
return true;
|
||||
|
||||
const htmlFiles=new Set(["/index","/login","/home","/register","/oauth2/auth"]);
|
||||
|
||||
|
||||
function isHtml(url:string):string|void{
|
||||
const path=new URL(url).pathname;
|
||||
if(htmlFiles.has(path)||htmlFiles.has(path+".html")){
|
||||
return path+path.endsWith(".html")?"":".html";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async function getfile(event: {
|
||||
request: { url: URL | RequestInfo; clone: () => string | URL | Request };
|
||||
}){
|
||||
let enabled="false";
|
||||
let offline=false;
|
||||
|
||||
function toPath(url:string):string{
|
||||
const Url= new URL(url);
|
||||
let html=isHtml(url);
|
||||
if(!html){
|
||||
const path=Url.pathname;
|
||||
if(path.startsWith("/channels")){
|
||||
html="./index.html"
|
||||
}else if(path.startsWith("/invite")){
|
||||
html="./invite.html"
|
||||
}
|
||||
}
|
||||
return html||Url.pathname;
|
||||
}
|
||||
let fails=0;
|
||||
async function getfile(event: FetchEvent):Promise<Response>{
|
||||
checkCache();
|
||||
if(!samedomain(event.request.url.toString())){
|
||||
return await fetch(event.request.clone());
|
||||
if(!samedomain(event.request.url)||enabled==="false"||(enabled==="offlineOnly"&&!offline)){
|
||||
const responce=await fetch(event.request.clone());
|
||||
if(samedomain(event.request.url)){
|
||||
if(enabled==="offlineOnly"&&responce.ok){
|
||||
putInCache(toPath(event.request.url),responce.clone());
|
||||
}
|
||||
const responseFromCache = await caches.match(event.request.url);
|
||||
console.log(responseFromCache, caches);
|
||||
if(!responce.ok){
|
||||
fails++;
|
||||
if(fails>5){
|
||||
offline=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return responce;
|
||||
}
|
||||
|
||||
let path=toPath(event.request.url);
|
||||
if(path === "/instances.json"){
|
||||
return await fetch(path);
|
||||
}
|
||||
console.log("Getting path: "+path);
|
||||
const responseFromCache = await caches.match(path);
|
||||
if(responseFromCache){
|
||||
console.log("cache hit");
|
||||
return responseFromCache;
|
||||
}
|
||||
if(isindexhtml(event.request.url.toString())){
|
||||
console.log("is index.html");
|
||||
const responseFromCache = await caches.match("/index.html");
|
||||
if(responseFromCache){
|
||||
console.log("cache hit");
|
||||
return responseFromCache;
|
||||
}
|
||||
const responseFromNetwork = await fetch("/index.html");
|
||||
await putInCache("/index.html", responseFromNetwork.clone());
|
||||
return responseFromNetwork;
|
||||
}
|
||||
const responseFromNetwork = await fetch(event.request.clone());
|
||||
console.log(event.request.clone());
|
||||
await putInCache(event.request.clone(), responseFromNetwork.clone());
|
||||
try{
|
||||
const responseFromNetwork = await fetch(path);
|
||||
if(responseFromNetwork.ok){
|
||||
await putInCache(path, responseFromNetwork.clone());
|
||||
}
|
||||
return responseFromNetwork;
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
return e;
|
||||
return new Response(null);
|
||||
}
|
||||
}
|
||||
self.addEventListener("fetch", (event: any)=>{
|
||||
|
||||
|
||||
self.addEventListener("fetch", (e)=>{
|
||||
const event=e as FetchEvent;
|
||||
try{
|
||||
event.respondWith(getfile(event));
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("message", (message)=>{
|
||||
const data=message.data;
|
||||
switch(data.code){
|
||||
case "setMode":
|
||||
enabled=data.data;
|
||||
break;
|
||||
case "CheckUpdate":
|
||||
checkedrecently=false;
|
||||
checkCache();
|
||||
break;
|
||||
case "ForceClear":
|
||||
deleteoldcache();
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
|
|
@ -30,6 +30,15 @@ class Buttons implements OptionsElement<unknown>{
|
|||
this.buttonList = buttonList;
|
||||
const htmlarea = document.createElement("div");
|
||||
htmlarea.classList.add("flexgrow");
|
||||
const buttonTable = this.generateButtons(htmlarea);
|
||||
if(this.buttons[0]){
|
||||
this.generateHTMLArea(this.buttons[0][1], htmlarea);
|
||||
}
|
||||
buttonList.append(buttonTable);
|
||||
buttonList.append(htmlarea);
|
||||
return buttonList;
|
||||
}
|
||||
generateButtons(optionsArea:HTMLElement){
|
||||
const buttonTable = document.createElement("div");
|
||||
buttonTable.classList.add("settingbuttons");
|
||||
for(const thing of this.buttons){
|
||||
|
@ -37,24 +46,21 @@ class Buttons implements OptionsElement<unknown>{
|
|||
button.classList.add("SettingsButton");
|
||||
button.textContent = thing[0];
|
||||
button.onclick = _=>{
|
||||
this.generateHTMLArea(thing[1], htmlarea);
|
||||
this.generateHTMLArea(thing[1], optionsArea);
|
||||
if(this.warndiv){
|
||||
this.warndiv.remove();
|
||||
}
|
||||
};
|
||||
buttonTable.append(button);
|
||||
}
|
||||
this.generateHTMLArea(this.buttons[0][1], htmlarea);
|
||||
buttonList.append(buttonTable);
|
||||
buttonList.append(htmlarea);
|
||||
return buttonList;
|
||||
return buttonTable;
|
||||
}
|
||||
handleString(str: string): HTMLElement{
|
||||
const div = document.createElement("span");
|
||||
div.textContent = str;
|
||||
return div;
|
||||
}
|
||||
private generateHTMLArea(
|
||||
generateHTMLArea(
|
||||
buttonInfo: Options | string,
|
||||
htmlarea: HTMLElement
|
||||
){
|
||||
|
@ -202,8 +208,8 @@ class CheckboxInput implements OptionsElement<boolean>{
|
|||
const input = this.input.deref();
|
||||
if(input){
|
||||
const value = input.checked as boolean;
|
||||
this.onchange(value);
|
||||
this.value = value;
|
||||
this.onchange(value);
|
||||
}
|
||||
}
|
||||
setState(state:boolean){
|
||||
|
@ -1066,6 +1072,10 @@ class Form implements OptionsElement<object>{
|
|||
this.owner.changed();
|
||||
}
|
||||
}
|
||||
preprocessor:(obj:Object)=>void=()=>{};
|
||||
addPreprocessor(func:(obj:Object)=>void){
|
||||
this.preprocessor=func;
|
||||
}
|
||||
async submit(){
|
||||
if(this.options.subOptions){
|
||||
this.options.subOptions.submit();
|
||||
|
@ -1130,6 +1140,7 @@ class Form implements OptionsElement<object>{
|
|||
}
|
||||
console.log("middle2");
|
||||
await Promise.allSettled(promises);
|
||||
this.preprocessor(build);
|
||||
if(this.fetchURL !== ""){
|
||||
fetch(this.fetchURL, {
|
||||
method: this.method,
|
||||
|
|
|
@ -581,6 +581,7 @@ span.instanceStatus {
|
|||
.channels {
|
||||
overflow-y: hidden;
|
||||
transition: height .2s ease-in-out;
|
||||
padding-left: 6px;
|
||||
}
|
||||
#channels > div > div:first-child {
|
||||
margin-top: 8px;
|
||||
|
@ -592,7 +593,7 @@ span.instanceStatus {
|
|||
}
|
||||
.channelbutton {
|
||||
height: 2em;
|
||||
padding: 0 8px;
|
||||
padding: 0 5px;
|
||||
background: transparent;
|
||||
color: var(--primary-text-soft);
|
||||
display: flex;
|
||||
|
@ -1934,3 +1935,28 @@ fieldset input[type="radio"] {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
.addrole{
|
||||
width:.1in;
|
||||
height: .1in;
|
||||
margin-left: .1in;
|
||||
margin-top: .04in;
|
||||
cursor: pointer;
|
||||
}
|
||||
.fixedsearch{
|
||||
position: absolute;
|
||||
background: var(--primary-bg);
|
||||
min-height: .2in;
|
||||
padding:.05in;
|
||||
border:solid .03in var(--black);
|
||||
border-radius:.05in;
|
||||
span{
|
||||
margin-top:.1in;
|
||||
width:100%;
|
||||
padding:.03in;
|
||||
border:solid .03in var(--black);
|
||||
box-sizing:border-box;
|
||||
border-radius:.05in;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import{ Localuser }from"./localuser.js";
|
|||
import{ Guild }from"./guild.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ presencejson, userjson }from"./jsontypes.js";
|
||||
import { Role } from "./role.js";
|
||||
import { Search } from "./search.js";
|
||||
|
||||
class User extends SnowFlake{
|
||||
owner: Localuser;
|
||||
|
@ -174,6 +176,58 @@ class User extends SnowFlake{
|
|||
return us.hasPermission("BAN_MEMBERS") || false;
|
||||
}
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
"Add roles",
|
||||
async function(this: User, member: Member | undefined,e){
|
||||
if(member){
|
||||
e.stopPropagation();
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of member.guild.roles){
|
||||
if(!role.canManage()||member.roles.indexOf(role)!==-1){
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
const result=await search.find(e.x,e.y);
|
||||
if(!result) return;
|
||||
member.addRole(result);
|
||||
}
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
const us = member.guild.member;
|
||||
console.log(us.hasPermission("MANAGE_ROLES"))
|
||||
return us.hasPermission("MANAGE_ROLES") || false;
|
||||
}
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
"Remove roles",
|
||||
async function(this: User, member: Member | undefined,e){
|
||||
if(member){
|
||||
e.stopPropagation();
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of member.roles){
|
||||
if(!role.canManage()){
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
const result=await search.find(e.x,e.y);
|
||||
if(!result) return;
|
||||
member.removeRole(result);
|
||||
}
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
const us = member.guild.member;
|
||||
console.log(us.hasPermission("MANAGE_ROLES"))
|
||||
return us.hasPermission("MANAGE_ROLES") || false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static checkuser(user: User | userjson, owner: Localuser): User{
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"incremental": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"DOM"
|
||||
"DOM",
|
||||
"webworker"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue