diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index e736979..98cccab 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -2,7 +2,7 @@ import {Message} from "./message.js"; import {AVoice} from "./audio/voice.js"; import {Contextmenu} from "./contextmenu.js"; -import {Guild} from "./guild.js"; +import {Guild, makeInviteMenu} from "./guild.js"; import {Localuser} from "./localuser.js"; import {Permissions} from "./permissions.js"; import {Dialog, Float, Settings} from "./settings.js"; @@ -270,6 +270,9 @@ class Channel extends SnowFlake { ), ); + const inviteMenu = settings.addButton(I18n.guild.invites()); + makeInviteMenu(inviteMenu, this.owner, this.info.api + `/channels/${this.id}/invites`); + const webhooks = settings.addButton(I18n.webhooks.base()); webhookMenu(this.guild, this.info.api + `/channels/${this.id}/webhooks`, webhooks, this.id); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index a4970f3..627de72 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -24,7 +24,138 @@ import {webhookMenu} from "./webhooks.js"; import {createImg} from "./utils/utils.js"; import {Sticker} from "./sticker.js"; import {ProgessiveDecodeJSON} from "./utils/progessiveLoad.js"; +export async function makeInviteMenu(inviteMenu: Options, guild: Guild, url: string) { + const invDiv = document.createElement("div"); + const bansp = ProgessiveDecodeJSON(url, { + headers: guild.headers, + }); + const createInviteHTML = (invite: invitejson) => { + const div = document.createElement("div"); + div.classList.add("templateMiniBox"); + const edit = document.createElement("button"); + edit.textContent = I18n.edit(); + + const code = document.createElement("span"); + code.textContent = invite.code; + + const used = document.createElement("span"); + used.textContent = I18n.invite.used(invite.uses + ""); + + edit.onclick = () => { + const opt = inviteMenu.addSubOptions(invite.code); + const inviter = new User(invite.inviter, guild.localuser); + + opt.addMDText( + window.location.origin + + "/invite/" + + invite.code + + "?" + + new URLSearchParams([["instance", guild.info.wellknown]]), + ); + + opt.addText(I18n.invite.used(invite.uses + "")); + if (invite.max_uses !== 0) opt.addText(I18n.invite.maxUses(invite.max_uses + "")); + + const channel = guild.channels.find((_) => _.id == invite.channel_id); + if (channel) { + opt.addText(I18n.invite.forChannel(channel.name)); + } + + opt.addText(I18n.invite.createdAt(new Date(invite.created_at).toLocaleDateString(I18n.lang))); + + let expires = I18n.invite.never(); + if (invite.expires_at) { + expires = new Date(invite.expires_at).toLocaleDateString(I18n.lang); + } + opt.addText(I18n.invite.expires(expires)); + + opt.addText(I18n.webhooks.createdBy()); + opt.addHTMLArea(inviter.createWidget(guild)); + + opt.addButtonInput("", I18n.delete(), async () => { + if ( + ( + await fetch(guild.info.api + "/invites/" + invite.code, { + method: "DELETE", + headers: guild.headers, + }) + ).ok + ) { + invsArr = invsArr.filter((_) => _ !== invite); + inviteMenu.returnFromSub(); + loadPage(currentPage); + } + }); + }; + + div.append(used, code, edit); + return div; + }; + let invsArr: invitejson[] = []; + let onpage = 0; + async function loadArr() { + let invsArr2: invitejson[] = []; + let waiting = false; + async function addHTML() { + if (waiting) return; + waiting = true; + await new Promise((res) => setTimeout(res, 0)); + waiting = false; + invDiv.append(...invsArr2.map((inv) => createInviteHTML(inv))); + invsArr2 = []; + } + while (!(await bansp).done) { + const inv = await (await (await bansp).getNext()).getWhole(); + invsArr.push(inv); + if (onpage < 50) { + invsArr2.push(inv); + addHTML(); + onpage++; + } else { + next.disabled = false; + } + } + } + let currentPage = 0; + function loadPage(page = 0) { + invDiv.innerHTML = ""; + for (onpage = 0; onpage < 50; onpage++) { + const inv = invsArr[onpage + page * 50]; + if (!inv) break; + invDiv.append(createInviteHTML(inv)); + } + if (onpage === 50 && invsArr[onpage + page * 50]) { + next.disabled = false; + } else { + next.disabled = true; + } + } + + const pageNav = document.createElement("div"); + const back = document.createElement("button"); + back.textContent = I18n.search.back(); + back.disabled = !currentPage; + back.onclick = () => { + back.disabled = !(currentPage - 1); + next.disabled = false; + loadPage(--currentPage); + }; + + const next = document.createElement("button"); + next.textContent = I18n.search.next(); + next.disabled = true; + pageNav.append(back, next); + inviteMenu.addHTMLArea(pageNav); + next.onclick = () => { + loadPage(++currentPage); + back.disabled = false; + }; + + loadArr(); + loadPage(currentPage); + inviteMenu.addHTMLArea(invDiv); +} class Guild extends SnowFlake { owner!: Localuser; headers!: Localuser["headers"]; @@ -426,6 +557,9 @@ class Guild extends SnowFlake { genDiv(); emoji.addHTMLArea(containdiv); } + const inviteMenu = settings.addButton(I18n.guild.invites()); + makeInviteMenu(inviteMenu, this, this.info.api + `/guilds/${this.id}/invites`); + const banMenu = settings.addButton(I18n.guild.bans()); const makeBanMenu = () => { const banDiv = document.createElement("div"); diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 49cdc98..d30a82e 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -467,10 +467,10 @@ type invitejson = { code: string; temporary: boolean; uses: number; - max_use: number; + max_uses: number; max_age: number; created_at: string; - expires_at: string; + expires_at: string | null; guild_id: string; channel_id: string; inviter_id: string; diff --git a/src/webpage/style.css b/src/webpage/style.css index 1bb5baf..a64ade8 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -66,7 +66,10 @@ body { border-radius: 4px; width: fit-content; background: var(--secondary-bg); - + margin-bottom: 8px; + span { + margin-right: 4px; + } button { margin-left: 4px; } diff --git a/translations/en.json b/translations/en.json index 436fefc..cf570ca 100644 --- a/translations/en.json +++ b/translations/en.json @@ -179,6 +179,7 @@ "createCatagory": "Create category", "permissions": "Permissions" }, + "delete": "Delete", "webhooks": { "createdAt": "Created at $1", "name": "Name:", @@ -259,6 +260,7 @@ "templateName": "Template Name:", "templateDesc": "Template Description:", "templcateMetaDesc": "A template allows others to use this guild as a base for their own guilds, it'll copy the channels, roles, and settings of this guild, but not the messages from within the guild, the bots, or the guilds icon.", + "invites": "Invites", "templateNameShort": "Template name must at least be 2 characters long", "templateURL": "Template URL: $1", "bannedBy": "Banned by:", @@ -474,7 +476,13 @@ "expireAfter": "Expire after:", "channel:": "Channel:", "inviteMaker": "Invite Maker", - "createInvite": "Create invite" + "createInvite": "Create invite", + "used": "Used $1 {{PLURAL:$1|time|times}}.", + "forChannel": "For channel: $1", + "createdAt": "Created at $1", + "expires": "Expires: $1", + "never": "Never", + "maxUses": "Max uses: $1" }, "friends": { "blocked": "Blocked",