roles
This commit is contained in:
parent
e06d304064
commit
c8e3125c5d
10 changed files with 759 additions and 250 deletions
|
@ -61,7 +61,7 @@ class Channel extends SnowFlake{
|
||||||
this.readbottom();
|
this.readbottom();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contextmenu.addbutton("Settings[temp]", function(this: Channel){
|
this.contextmenu.addbutton("Settings", function(this: Channel){
|
||||||
this.generateSettings();
|
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(
|
this.contextmenu.addbutton(
|
||||||
"Make invite",
|
"Make invite",
|
||||||
function(this: Channel){
|
function(this: Channel){
|
||||||
|
@ -205,15 +194,33 @@ class Channel extends SnowFlake{
|
||||||
generateSettings(){
|
generateSettings(){
|
||||||
this.sortPerms();
|
this.sortPerms();
|
||||||
const settings = new Settings("Settings for " + this.name);
|
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(
|
s1.options.push(
|
||||||
new RoleList(
|
new RoleList(
|
||||||
this.permission_overwritesar,
|
this.permission_overwritesar,
|
||||||
this.guild,
|
this.guild,
|
||||||
this.updateRolePermissions.bind(this),
|
this.updateRolePermissions.bind(this),
|
||||||
true
|
this
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
settings.show();
|
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(){
|
deleteChannel(){
|
||||||
fetch(this.info.api + "/channels/" + this.id, {
|
fetch(this.info.api + "/channels/" + this.id, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
@ -1231,12 +1176,11 @@ class Channel extends SnowFlake{
|
||||||
|
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.guild_id = json.guild_id;
|
this.guild_id = json.guild_id;
|
||||||
|
const oldover=this.permission_overwrites;
|
||||||
this.permission_overwrites = new Map();
|
this.permission_overwrites = new Map();
|
||||||
|
this.permission_overwritesar=[];
|
||||||
for(const thing of json.permission_overwrites){
|
for(const thing of json.permission_overwrites){
|
||||||
if(
|
if(thing.id === "1182819038095799904" || thing.id === "1182820803700625444"){
|
||||||
thing.id === "1182819038095799904" ||
|
|
||||||
thing.id === "1182820803700625444"
|
|
||||||
){
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.permission_overwrites.set(
|
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.topic = json.topic;
|
||||||
this.nsfw = json.nsfw;
|
this.nsfw = json.nsfw;
|
||||||
}
|
}
|
||||||
|
croleUpdate:(role:Role,perm:Permissions,added:boolean)=>unknown=()=>{};
|
||||||
typingstart(){
|
typingstart(){
|
||||||
if(this.typing > Date.now()){
|
if(this.typing > Date.now()){
|
||||||
return;
|
return;
|
||||||
|
@ -1366,16 +1327,14 @@ class Channel extends SnowFlake{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
this.localuser.lookingguild?.prevchannel === this &&
|
this.localuser.lookingguild?.prevchannel === this && document.hasFocus()
|
||||||
document.hasFocus()
|
|
||||||
){
|
){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.notification === "all"){
|
if(this.notification === "all"){
|
||||||
this.notify(messagez);
|
this.notify(messagez);
|
||||||
}else if(
|
}else if(
|
||||||
this.notification === "mentions" &&
|
this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)
|
||||||
messagez.mentionsuser(this.localuser.user)
|
|
||||||
){
|
){
|
||||||
this.notify(messagez);
|
this.notify(messagez);
|
||||||
}
|
}
|
||||||
|
@ -1445,21 +1404,23 @@ class Channel extends SnowFlake{
|
||||||
if(permission){
|
if(permission){
|
||||||
permission.allow = perms.allow;
|
permission.allow = perms.allow;
|
||||||
permission.deny = perms.deny;
|
permission.deny = perms.deny;
|
||||||
|
}else{
|
||||||
|
//this.permission_overwrites.set(id,perms);
|
||||||
|
}
|
||||||
await fetch(
|
await fetch(
|
||||||
this.info.api + "/channels/" + this.id + "/permissions/" + id,
|
this.info.api + "/channels/" + this.id + "/permissions/" + id,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
allow: permission.allow.toString(),
|
allow: perms.allow.toString(),
|
||||||
deny: permission.deny.toString(),
|
deny: perms.deny.toString(),
|
||||||
id,
|
id,
|
||||||
type: 0,
|
type: 0,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Channel.setupcontextmenu();
|
Channel.setupcontextmenu();
|
||||||
export{ Channel };
|
export{ Channel };
|
||||||
|
|
|
@ -13,6 +13,7 @@ import{
|
||||||
emojijson,
|
emojijson,
|
||||||
memberjson,
|
memberjson,
|
||||||
invitejson,
|
invitejson,
|
||||||
|
rolesjson,
|
||||||
}from"./jsontypes.js";
|
}from"./jsontypes.js";
|
||||||
import{ User }from"./user.js";
|
import{ User }from"./user.js";
|
||||||
|
|
||||||
|
@ -114,16 +115,67 @@ class Guild extends SnowFlake{
|
||||||
}
|
}
|
||||||
form.addTextInput("Region:", "region", { initText: region });
|
form.addTextInput("Region:", "region", { initText: region });
|
||||||
}
|
}
|
||||||
const s1 = settings.addButton("roles");
|
const s1 = settings.addButton("Roles");
|
||||||
const permlist: [Role, Permissions][] = [];
|
const permlist: [Role, Permissions][] = [];
|
||||||
for(const thing of this.roles){
|
for(const thing of this.roles){
|
||||||
permlist.push([thing, thing.permissions]);
|
permlist.push([thing, thing.permissions]);
|
||||||
}
|
}
|
||||||
s1.options.push(
|
s1.options.push(
|
||||||
new RoleList(permlist, this, this.updateRolePermissions.bind(this))
|
new RoleList(permlist, this, this.updateRolePermissions.bind(this),false)
|
||||||
);
|
);
|
||||||
settings.show();
|
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(
|
constructor(
|
||||||
json: guildjson | -1,
|
json: guildjson | -1,
|
||||||
owner: Localuser,
|
owner: Localuser,
|
||||||
|
@ -153,6 +205,7 @@ class Guild extends SnowFlake{
|
||||||
this.roles.push(roleh);
|
this.roles.push(roleh);
|
||||||
this.roleids.set(roleh.id, roleh);
|
this.roleids.set(roleh.id, roleh);
|
||||||
}
|
}
|
||||||
|
this.sortRoles();
|
||||||
if(member instanceof User){
|
if(member instanceof User){
|
||||||
Member.resolveMember(member, this).then(_=>{
|
Member.resolveMember(member, this).then(_=>{
|
||||||
if(_){
|
if(_){
|
||||||
|
|
|
@ -478,7 +478,28 @@ roleCreate | {
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
emoji: emojijson;
|
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;
|
}|memberlistupdatejson|voiceupdate|voiceserverupdate;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -494,7 +494,32 @@ class Localuser{
|
||||||
this.voiceFactory.voiceServerUpdate(temp)
|
this.voiceFactory.voiceServerUpdate(temp)
|
||||||
}
|
}
|
||||||
break;
|
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){
|
}else if(temp.op === 10){
|
||||||
|
|
|
@ -49,6 +49,32 @@ class Member extends SnowFlake{
|
||||||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
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(){
|
get guild(){
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
@ -241,6 +267,24 @@ class Member extends SnowFlake{
|
||||||
]);
|
]);
|
||||||
menu.show();
|
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){
|
banAPI(reason: string){
|
||||||
const headers = structuredClone(this.guild.headers);
|
const headers = structuredClone(this.guild.headers);
|
||||||
(headers as any)["x-audit-log-reason"] = reason;
|
(headers as any)["x-audit-log-reason"] = reason;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import{ Localuser }from"./localuser.js";
|
||||||
import{ Guild }from"./guild.js";
|
import{ Guild }from"./guild.js";
|
||||||
import{ SnowFlake }from"./snowflake.js";
|
import{ SnowFlake }from"./snowflake.js";
|
||||||
import{ rolesjson }from"./jsontypes.js";
|
import{ rolesjson }from"./jsontypes.js";
|
||||||
|
import{ Search }from"./search.js";
|
||||||
class Role extends SnowFlake{
|
class Role extends SnowFlake{
|
||||||
permissions: Permissions;
|
permissions: Permissions;
|
||||||
owner: Guild;
|
owner: Guild;
|
||||||
|
@ -13,6 +14,7 @@ class Role extends SnowFlake{
|
||||||
icon!: string;
|
icon!: string;
|
||||||
mentionable!: boolean;
|
mentionable!: boolean;
|
||||||
unicode_emoji!: string;
|
unicode_emoji!: string;
|
||||||
|
position!:number;
|
||||||
headers: Guild["headers"];
|
headers: Guild["headers"];
|
||||||
constructor(json: rolesjson, owner: Guild){
|
constructor(json: rolesjson, owner: Guild){
|
||||||
super(json.id);
|
super(json.id);
|
||||||
|
@ -27,6 +29,15 @@ class Role extends SnowFlake{
|
||||||
this.permissions = new Permissions(json.permissions);
|
this.permissions = new Permissions(json.permissions);
|
||||||
this.owner = owner;
|
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{
|
get guild(): Guild{
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +50,14 @@ class Role extends SnowFlake{
|
||||||
}
|
}
|
||||||
return`#${this.color.toString(16)}`;
|
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 };
|
export{ Role };
|
||||||
import{ Options }from"./settings.js";
|
import{ Options }from"./settings.js";
|
||||||
|
@ -121,22 +140,25 @@ class PermissionToggle implements OptionsElement<number>{
|
||||||
submit(){}
|
submit(){}
|
||||||
}
|
}
|
||||||
import{ OptionsElement, Buttons }from"./settings.js";
|
import{ OptionsElement, Buttons }from"./settings.js";
|
||||||
|
import { Contextmenu } from "./contextmenu.js";
|
||||||
|
import { Channel } from "./channel.js";
|
||||||
class RoleList extends Buttons{
|
class RoleList extends Buttons{
|
||||||
readonly permissions: [Role, Permissions][];
|
permissions: [Role, Permissions][];
|
||||||
permission: Permissions;
|
permission: Permissions;
|
||||||
readonly guild: Guild;
|
readonly guild: Guild;
|
||||||
readonly channel: boolean;
|
readonly channel: false|Channel;
|
||||||
declare readonly buttons: [string, string][];
|
declare buttons: [string, string][];
|
||||||
readonly options: Options;
|
readonly options: Options;
|
||||||
onchange: Function;
|
onchange: Function;
|
||||||
curid!: string;
|
curid?: string;
|
||||||
constructor(
|
get info(){
|
||||||
permissions: [Role, Permissions][],
|
return this.guild.info;
|
||||||
guild: Guild,
|
}
|
||||||
onchange: Function,
|
get headers(){
|
||||||
channel = false
|
return this.guild.headers;
|
||||||
){
|
}
|
||||||
super("Roles");
|
constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){
|
||||||
|
super("");
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
@ -147,16 +169,237 @@ class RoleList extends Buttons{
|
||||||
}else{
|
}else{
|
||||||
this.permission = new Permissions("0");
|
this.permission = new Permissions("0");
|
||||||
}
|
}
|
||||||
|
this.makeguildmenus(options);
|
||||||
for(const thing of Permissions.info){
|
for(const thing of Permissions.info){
|
||||||
options.options.push(
|
options.options.push(
|
||||||
new PermissionToggle(thing, this.permission, options)
|
new PermissionToggle(thing, this.permission, options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for(const i of permissions){
|
for(const i of permissions){
|
||||||
console.log(i);
|
|
||||||
this.buttons.push([i[0].name, i[0].id]);
|
this.buttons.push([i[0].name, i[0].id]);
|
||||||
}
|
}
|
||||||
this.options = options;
|
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
|
||||||
|
});
|
||||||
|
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{
|
handleString(str: string): HTMLElement{
|
||||||
this.curid = str;
|
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};
|
|
@ -30,6 +30,15 @@ class Buttons implements OptionsElement<unknown>{
|
||||||
this.buttonList = buttonList;
|
this.buttonList = buttonList;
|
||||||
const htmlarea = document.createElement("div");
|
const htmlarea = document.createElement("div");
|
||||||
htmlarea.classList.add("flexgrow");
|
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");
|
const buttonTable = document.createElement("div");
|
||||||
buttonTable.classList.add("settingbuttons");
|
buttonTable.classList.add("settingbuttons");
|
||||||
for(const thing of this.buttons){
|
for(const thing of this.buttons){
|
||||||
|
@ -37,24 +46,21 @@ class Buttons implements OptionsElement<unknown>{
|
||||||
button.classList.add("SettingsButton");
|
button.classList.add("SettingsButton");
|
||||||
button.textContent = thing[0];
|
button.textContent = thing[0];
|
||||||
button.onclick = _=>{
|
button.onclick = _=>{
|
||||||
this.generateHTMLArea(thing[1], htmlarea);
|
this.generateHTMLArea(thing[1], optionsArea);
|
||||||
if(this.warndiv){
|
if(this.warndiv){
|
||||||
this.warndiv.remove();
|
this.warndiv.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
buttonTable.append(button);
|
buttonTable.append(button);
|
||||||
}
|
}
|
||||||
this.generateHTMLArea(this.buttons[0][1], htmlarea);
|
return buttonTable;
|
||||||
buttonList.append(buttonTable);
|
|
||||||
buttonList.append(htmlarea);
|
|
||||||
return buttonList;
|
|
||||||
}
|
}
|
||||||
handleString(str: string): HTMLElement{
|
handleString(str: string): HTMLElement{
|
||||||
const div = document.createElement("span");
|
const div = document.createElement("span");
|
||||||
div.textContent = str;
|
div.textContent = str;
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
private generateHTMLArea(
|
generateHTMLArea(
|
||||||
buttonInfo: Options | string,
|
buttonInfo: Options | string,
|
||||||
htmlarea: HTMLElement
|
htmlarea: HTMLElement
|
||||||
){
|
){
|
||||||
|
@ -1066,6 +1072,10 @@ class Form implements OptionsElement<object>{
|
||||||
this.owner.changed();
|
this.owner.changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
preprocessor:(obj:Object)=>void=()=>{};
|
||||||
|
addPreprocessor(func:(obj:Object)=>void){
|
||||||
|
this.preprocessor=func;
|
||||||
|
}
|
||||||
async submit(){
|
async submit(){
|
||||||
if(this.options.subOptions){
|
if(this.options.subOptions){
|
||||||
this.options.subOptions.submit();
|
this.options.subOptions.submit();
|
||||||
|
@ -1130,6 +1140,7 @@ class Form implements OptionsElement<object>{
|
||||||
}
|
}
|
||||||
console.log("middle2");
|
console.log("middle2");
|
||||||
await Promise.allSettled(promises);
|
await Promise.allSettled(promises);
|
||||||
|
this.preprocessor(build);
|
||||||
if(this.fetchURL !== ""){
|
if(this.fetchURL !== ""){
|
||||||
fetch(this.fetchURL, {
|
fetch(this.fetchURL, {
|
||||||
method: this.method,
|
method: this.method,
|
||||||
|
|
|
@ -1934,3 +1934,28 @@ fieldset input[type="radio"] {
|
||||||
width: 100%;
|
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{ Guild }from"./guild.js";
|
||||||
import{ SnowFlake }from"./snowflake.js";
|
import{ SnowFlake }from"./snowflake.js";
|
||||||
import{ presencejson, userjson }from"./jsontypes.js";
|
import{ presencejson, userjson }from"./jsontypes.js";
|
||||||
|
import { Role } from "./role.js";
|
||||||
|
import { Search } from "./search.js";
|
||||||
|
|
||||||
class User extends SnowFlake{
|
class User extends SnowFlake{
|
||||||
owner: Localuser;
|
owner: Localuser;
|
||||||
|
@ -174,6 +176,58 @@ class User extends SnowFlake{
|
||||||
return us.hasPermission("BAN_MEMBERS") || false;
|
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{
|
static checkuser(user: User | userjson, owner: Localuser): User{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue