From d33d7231adff399c1c3d05a4d6cb3c2b02e7157e Mon Sep 17 00:00:00 2001 From: ygg2 Date: Wed, 30 Oct 2024 20:57:07 -0400 Subject: [PATCH 0001/1330] channel indentation maintain dots --- src/webpage/style.css | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 995ccaba..d566ebb4 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -581,19 +581,18 @@ span.instanceStatus { .channels { overflow-y: hidden; transition: height .2s ease-in-out; - padding-left: 6px; } #channels > div > div:first-child { - margin-top: 8px; + margin-top: 6px; } .channel { - margin: 0 8px; + margin: 0 6px; display: flex; flex-direction: column; } .channelbutton { height: 2em; - padding: 0 5px; + padding: 0 8px; background: transparent; color: var(--primary-text-soft); display: flex; @@ -604,6 +603,9 @@ span.instanceStatus { background: var(--channel-hover); color: var(--primary-text-prominent); } +.channels .channelbutton { + margin-left: 8px; +} .viewChannel .channelbutton, .viewChannel .channelbutton:hover { background: var(--channel-selected); font-weight: bold; @@ -617,7 +619,7 @@ span.instanceStatus { content: ''; position: absolute; top: calc(50% - 4px); - left: -12px; + left: -10px; height: 8px; width: 8px; background: var(--primary-text); From 41e89a1f6b119f64f6b7c04f4b7094a1b7ed4635 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 20:20:53 -0500 Subject: [PATCH 0002/1330] fix side list --- src/webpage/localuser.ts | 10 +++++++--- src/webpage/login.ts | 4 +--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 7284ca9c..589ff7c4 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -602,13 +602,14 @@ class Localuser{ } } - const elms:Map=new Map([["offline",[]],["online",[]]]); + const elms:Map=new Map([]); for(const role of guild.roles){ - console.log(guild.roles); if(role.hoist){ elms.set(role,[]); } } + elms.set("online",[]); + elms.set("offline",[]) const members=new Set(guild.members); members.forEach((member)=>{ if(!channel.hasPermission("VIEW_CHANNEL",member)){ @@ -626,6 +627,9 @@ class Localuser{ } return; } + if(member.user.status === "offline"){ + return; + } if(role !== "online"&&member.hasRole(role.id)){ list.push(member); members.delete(member); @@ -671,7 +675,7 @@ class Localuser{ membershtml.append(memberdiv); } category.append(membershtml); - div.prepend(category); + div.append(category); } console.log(elms); diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 3eaac205..2c4973be 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -474,9 +474,7 @@ async function login(username: string, password: string, captcha: string){ console.warn(res); if(!res.token)return; adduser({ - serverurls: JSON.parse( - localStorage.getItem("instanceinfo")! - ), + serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string), email: username, token: res.token, }).username = username; From e7de38ed9412af4e263e1994c9e14c9223df43e4 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Wed, 30 Oct 2024 21:26:19 -0400 Subject: [PATCH 0003/1330] fix submenu height / make settingbuttons like channels --- src/webpage/style.css | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index d566ebb4..d8d8492f 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1663,9 +1663,10 @@ fieldset input[type="radio"] { .settingbuttons { flex: none; width: 192px; - padding: 8px; + padding: 6px; background: var(--settings-panel-bg); border-right: 2px solid var(--shadow); + border-left: 2px solid transparent; box-sizing: border-box; overflow-y: auto; } @@ -1681,14 +1682,22 @@ fieldset input[type="radio"] { background: var(--settings-panel-hover); color: var(--primary-text-prominent); } +.addrole { + height: 10px; + width: 10px; + margin-left: auto; + align-self: center; + cursor: pointer; +} .titlediv { - max-height: 100%; + height: 100%; } .flexspace { overflow-y: auto; padding-bottom: 32px; } .flexspace:has(.flexspace) { + height: 100%; padding-bottom: 0; } .optionElement, .FormSettings > button { @@ -1699,6 +1708,7 @@ fieldset input[type="radio"] { margin: 0; } .optionElement:has(.Buttons) { + height: 100%; min-height: 0; display: flex; } @@ -1937,13 +1947,7 @@ 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); From 9209744321f0cf701ec696177fde475f40cfd28e Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 20:51:20 -0500 Subject: [PATCH 0004/1330] style changes --- src/webpage/role.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 24a07f17..df1444fe 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -338,18 +338,21 @@ class RoleList extends Buttons{ console.log(found); this.onchange(found.id,new Permissions("0","0")); }else{ + const div=document.createElement("div"); 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); + div.classList.add("fixedsearch","OptionList"); + bar.type="text"; + div.style.left=(box.left^0)+"px"; + div.style.top=(box.top^0)+"px"; + div.append(bar) + document.body.append(div); if(Contextmenu.currentmenu != ""){ Contextmenu.currentmenu.remove(); } - Contextmenu.currentmenu=bar; - Contextmenu.keepOnScreen(bar); + Contextmenu.currentmenu=div; + Contextmenu.keepOnScreen(div); bar.onchange=()=>{ - bar.remove(); + div.remove(); console.log(bar.value) if(bar.value==="") return; fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{ From dae2e3e0e4eff866442b8929875ae296c1fb1acf Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 30 Oct 2024 22:24:03 -0500 Subject: [PATCH 0005/1330] trust proxy? --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 4e828002..a2990db0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -140,6 +140,8 @@ app.use("/", async (req: Request, res: Response)=>{ } }); +app.set('trust proxy', (ip:string) => ip.startsWith("127.")); + const PORT = process.env.PORT || Number(process.argv[2]) || 8080; app.listen(PORT, ()=>{ console.log(`Server running on port ${PORT}`); From fc32d96e021c1f58452ea364b365fe08d4da1e3e Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 00:41:39 -0400 Subject: [PATCH 0006/1330] adding roles for mobile --- src/webpage/style.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index d8d8492f..81ccb127 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1919,7 +1919,7 @@ fieldset input[type="radio"] { width: 100%; box-sizing: border-box; border-radius: 16px 16px 0 0; - box-shadow: 0 0 14px var(--shadow), 0 0 28px var(--shadow) + box-shadow: 0 0 14px var(--shadow), 0 0 28px var(--shadow); } .contextbutton { width: 100%; @@ -1946,6 +1946,12 @@ fieldset input[type="radio"] { height: 100px; width: 100%; } + .fixedsearch { + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%); + min-width: 80svw; + } } .fixedsearch{ From 6a66fadaed2370af1f313490a461d9686cf2bfa9 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 00:45:48 -0400 Subject: [PATCH 0007/1330] not hide it under scrollbar --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 81ccb127..8c58bdca 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1952,6 +1952,9 @@ fieldset input[type="radio"] { transform: translate(-50%, -50%); min-width: 80svw; } + .flexltr:has(>.addrole) { + margin: 6px 12px; + } } .fixedsearch{ From ac26d313d4a42b7f62bd8495979186825334dd59 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 00:52:28 -0400 Subject: [PATCH 0008/1330] made that a class instead --- src/webpage/role.ts | 2 +- src/webpage/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/role.ts b/src/webpage/role.ts index df1444fe..7b096e2d 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -314,7 +314,7 @@ class RoleList extends Buttons{ buttonTable.classList.add("flexttb"); const roleRow=document.createElement("div"); - roleRow.classList.add("flexltr"); + roleRow.classList.add("flexltr","rolesheader"); roleRow.append("Roles"); const add=document.createElement("span"); add.classList.add("svg-plus","svgicon","addrole"); diff --git a/src/webpage/style.css b/src/webpage/style.css index 8c58bdca..d2047c2e 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1952,7 +1952,7 @@ fieldset input[type="radio"] { transform: translate(-50%, -50%); min-width: 80svw; } - .flexltr:has(>.addrole) { + .rolesheader { margin: 6px 12px; } } From 6468281cf268cc80909089197296c795c426b319 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 01:35:03 -0400 Subject: [PATCH 0009/1330] iOS test --- src/webpage/contextmenu.ts | 17 ++++++++++------- src/webpage/login.ts | 2 ++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 903b72a4..a5559db1 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,3 +1,4 @@ +import{ iOS }from"./login.js"; class Contextmenu{ static currentmenu: HTMLElement | ""; name: string; @@ -90,13 +91,15 @@ class Contextmenu{ this.makemenu(event.clientX, event.clientY, addinfo, other); }; obj.addEventListener("contextmenu", func); - obj.addEventListener("touchstart",(event: TouchEvent)=>{ - if(event.touches.length > 1){ - event.preventDefault(); - event.stopImmediatePropagation(); - this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); - } - },{passive:true}); + if(iOS){ + obj.addEventListener("touchstart",(event: TouchEvent)=>{ + if(event.touches.length > 1){ + event.preventDefault(); + event.stopImmediatePropagation(); + this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); + } + },{passive: false}); + } return func; } static keepOnScreen(obj: HTMLElement){ diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 2c4973be..a1f765ac 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,6 +1,7 @@ import{ Dialog }from"./dialog.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); +const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); function setTheme(){ let name = localStorage.getItem("theme"); @@ -600,6 +601,7 @@ export{ checkInstance }; trimswitcher(); export{ mobile, + iOS, getBulkUsers, getBulkInfo, setTheme, From be5ebc2747cc04b0abacf1366458dee93b41aeb8 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 01:44:59 -0400 Subject: [PATCH 0010/1330] typo --- src/webpage/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 21d925cd..227bfc8b 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -214,7 +214,7 @@ class Channel extends SnowFlake{ obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string] }) } - const s1 = settings.addButton("Permisions"); + const s1 = settings.addButton("Permissions"); s1.options.push( new RoleList( this.permission_overwritesar, From 0958d04dd82f6d00abd024f5adaade80c362b918 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Thu, 31 Oct 2024 02:28:18 -0400 Subject: [PATCH 0011/1330] fix other bug --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index d2047c2e..c7fa9e9c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1082,6 +1082,9 @@ span .quote:last-of-type .quoteline { .replytext a { pointer-events: none; } +.replytext pre { + display: inline; +} .reply { flex: 1; background: var(--reply-line); From fa4e3b18a72ee2736d1f89bde7986b31251f9cf5 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 12:42:54 -0500 Subject: [PATCH 0012/1330] don't cache non-ok server responces --- src/webpage/service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/service.ts b/src/webpage/service.ts index 5c2f0a3e..7209cf4c 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -30,6 +30,7 @@ async function checkCache(){ } console.log(lastcache); fetch("/getupdates").then(async data=>{ + if(!data.ok) return; const text = await data.clone().text(); console.log(text, lastcache); if(lastcache !== text){ From ff76e91ff897e1e5ca03a56c94ab41ea92ad4929 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 12:43:22 -0500 Subject: [PATCH 0013/1330] more timeout --- src/webpage/service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webpage/service.ts b/src/webpage/service.ts index 7209cf4c..b2bf2881 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -30,6 +30,9 @@ async function checkCache(){ } console.log(lastcache); fetch("/getupdates").then(async data=>{ + setTimeout((_: any)=>{ + checkedrecently = false; + }, 1000 * 60 * 30); if(!data.ok) return; const text = await data.clone().text(); console.log(text, lastcache); @@ -38,9 +41,6 @@ async function checkCache(){ putInCache("/getupdates", data); } checkedrecently = true; - setTimeout((_: any)=>{ - checkedrecently = false; - }, 1000 * 60 * 30); }); } var checkedrecently = false; From 7f95aff2d18ec9c8489f174f0891971759b9dc00 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 20:56:00 -0500 Subject: [PATCH 0014/1330] enable translation support for permisions --- src/index.ts | 4 +- src/webpage/bot.ts | 2 +- src/webpage/i18n.ts | 23 +- src/webpage/oauth2/auth.ts | 2 +- src/webpage/permissions.ts | 347 ++++++------------------------- src/webpage/role.ts | 2 +- src/webpage/translations/en.json | 106 +++++++++- 7 files changed, 194 insertions(+), 292 deletions(-) diff --git a/src/index.ts b/src/index.ts index a2990db0..620b25e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,9 +30,7 @@ app.use(compression()); async function updateInstances(): Promise{ try{ - const response = await fetch( - "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" - ); + const response = await fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"); const json = (await response.json()) as Instance[]; for(const instance of json){ if(instanceNames.has(instance.name)){ diff --git a/src/webpage/bot.ts b/src/webpage/bot.ts index e83154f1..64cf641e 100644 --- a/src/webpage/bot.ts +++ b/src/webpage/bot.ts @@ -259,7 +259,7 @@ class Bot{ params.set("scope", "bot"); const url=gen.addText(""); const perms=new Permissions("0"); - for(const perm of Permissions.info){ + for(const perm of Permissions.info()){ const permsisions=new PermissionToggle(perm,perms,gen); gen.options.push(permsisions); gen.generate(permsisions); diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 7f7094e2..c30ad7b6 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -1,10 +1,10 @@ type translation={ - [key:string]:string|{[key:string]:string} + [key:string]:string|translation }; let res:()=>unknown=()=>{}; class I18n{ static lang:string; - static translations:{[key:string]:string}[]=[]; + static translations:translation[]=[]; static done=new Promise((res2,_reject)=>{ res=res2; }); @@ -12,7 +12,7 @@ class I18n{ if(typeof json === "string"){ json=await (await fetch(json)).json() as translation; } - const translations:{[key:string]:string}[]=[]; + const translations:translation[]=[]; let translation=json[lang]; if(!translation){ translation=json[lang[0]+lang[1]]; @@ -32,9 +32,20 @@ class I18n{ } static getTranslation(msg:string,...params:string[]):string{ let str:string|undefined; + const path=msg.split("."); for(const json of this.translations){ - str=json[msg]; - if(str){ + let jsont:string|translation=json; + for(const thing of path){ + if(typeof jsont !== "string" ){ + jsont=jsont[thing]; + }else{ + jsont=json; + break; + } + } + + if(typeof jsont === "string"){ + str=jsont; break; } } @@ -85,7 +96,7 @@ class I18n{ }); return msg; } - private static async toTranslation(trans:string|{[key:string]:string},lang:string):Promise<{[key:string]:string}>{ + private static async toTranslation(trans:string|translation,lang:string):Promise{ if(typeof trans==='string'){ return this.toTranslation((await (await fetch(trans)).json() as translation)[lang],lang); }else{ diff --git a/src/webpage/oauth2/auth.ts b/src/webpage/oauth2/auth.ts index bef5131b..b90f362e 100644 --- a/src/webpage/oauth2/auth.ts +++ b/src/webpage/oauth2/auth.ts @@ -231,7 +231,7 @@ type botjsonfetch={ if(perms&&permstr){ const permisions=new Permissions(permstr) - for(const perm of Permissions.info){ + for(const perm of Permissions.info()){ if(permisions.hasPermission(perm.name,false)){ const div=document.createElement("div"); const h2=document.createElement("h2"); diff --git a/src/webpage/permissions.ts b/src/webpage/permissions.ts index 21d80d88..cd05d528 100644 --- a/src/webpage/permissions.ts +++ b/src/webpage/permissions.ts @@ -1,3 +1,5 @@ +import { I18n } from "./i18n.js"; + class Permissions{ allow: bigint; deny: bigint; @@ -22,287 +24,75 @@ class Permissions{ const bit = 1n << BigInt(b); return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 } - static map: { -[key: number | string]: -| { name: string; readableName: string; description: string } -| number; -}; - static info: { name: string; readableName: string; description: string }[]; - static makeMap(){ - Permissions.info = [ - //for people in the future, do not reorder these, the creation of the map realize on the order - { - name: "CREATE_INSTANT_INVITE", - readableName: "Create invite", - description: "Allows the user to create invites for the guild", - }, - { - name: "KICK_MEMBERS", - readableName: "Kick members", - description: "Allows the user to kick members from the guild", - }, - { - name: "BAN_MEMBERS", - readableName: "Ban members", - description: "Allows the user to ban members from the guild", - }, - { - name: "ADMINISTRATOR", - readableName: "Administrator", - description: -"Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", - }, - { - name: "MANAGE_CHANNELS", - readableName: "Manage channels", - description: "Allows the user to manage and edit channels", - }, - { - name: "MANAGE_GUILD", - readableName: "Manage guild", - description: "Allows management and editing of the guild", - }, - { - name: "ADD_REACTIONS", - readableName: "Add reactions", - description: "Allows user to add reactions to messages", - }, - { - name: "VIEW_AUDIT_LOG", - readableName: "View audit log", - description: "Allows the user to view the audit log", - }, - { - name: "PRIORITY_SPEAKER", - readableName: "Priority speaker", - description: "Allows for using priority speaker in a voice channel", - }, - { - name: "STREAM", - readableName: "Video", - description: "Allows the user to stream", - }, - { - name: "VIEW_CHANNEL", - readableName: "View channels", - description: "Allows the user to view the channel", - }, - { - name: "SEND_MESSAGES", - readableName: "Send messages", - description: "Allows user to send messages", - }, - { - name: "SEND_TTS_MESSAGES", - readableName: "Send text-to-speech messages", - description: "Allows the user to send text-to-speech messages", - }, - { - name: "MANAGE_MESSAGES", - readableName: "Manage messages", - description: "Allows the user to delete messages that aren't their own", - }, - { - name: "EMBED_LINKS", - readableName: "Embed links", - description: "Allow links sent by this user to auto-embed", - }, - { - name: "ATTACH_FILES", - readableName: "Attach files", - description: "Allows the user to attach files", - }, - { - name: "READ_MESSAGE_HISTORY", - readableName: "Read message history", - description: "Allows user to read the message history", - }, - { - name: "MENTION_EVERYONE", - readableName: "Mention @everyone, @here and all roles", - description: "Allows the user to mention everyone", - }, - { - name: "USE_EXTERNAL_EMOJIS", - readableName: "Use external emojis", - description: "Allows the user to use external emojis", - }, - { - name: "VIEW_GUILD_INSIGHTS", - readableName: "View guild insights", - description: "Allows the user to see guild insights", - }, - { - name: "CONNECT", - readableName: "Connect", - description: "Allows the user to connect to a voice channel", - }, - { - name: "SPEAK", - readableName: "Speak", - description: "Allows the user to speak in a voice channel", - }, - { - name: "MUTE_MEMBERS", - readableName: "Mute members", - description: "Allows user to mute other members", - }, - { - name: "DEAFEN_MEMBERS", - readableName: "Deafen members", - description: "Allows user to deafen other members", - }, - { - name: "MOVE_MEMBERS", - readableName: "Move members", - description: "Allows the user to move members between voice channels", - }, - { - name: "USE_VAD", - readableName: "Use voice activity detection", - description: -"Allows users to speak in a voice channel by simply talking", - }, - { - name: "CHANGE_NICKNAME", - readableName: "Change nickname", - description: "Allows the user to change their own nickname", - }, - { - name: "MANAGE_NICKNAMES", - readableName: "Manage nicknames", - description: "Allows user to change nicknames of other members", - }, - { - name: "MANAGE_ROLES", - readableName: "Manage roles", - description: "Allows user to edit and manage roles", - }, - { - name: "MANAGE_WEBHOOKS", - readableName: "Manage webhooks", - description: "Allows management and editing of webhooks", - }, - { - name: "MANAGE_GUILD_EXPRESSIONS", - readableName: "Manage expressions", - description: "Allows for managing emoji, stickers, and soundboards", - }, - { - name: "USE_APPLICATION_COMMANDS", - readableName: "Use application commands", - description: "Allows the user to use application commands", - }, - { - name: "REQUEST_TO_SPEAK", - readableName: "Request to speak", - description: "Allows user to request to speak in stage channel", - }, - { - name: "MANAGE_EVENTS", - readableName: "Manage events", - description: "Allows user to edit and manage events", - }, - { - name: "MANAGE_THREADS", - readableName: "Manage threads", - description: -"Allows the user to delete and archive threads and view all private threads", - }, - { - name: "CREATE_PUBLIC_THREADS", - readableName: "Create public threads", - description: "Allows the user to create public threads", - }, - { - name: "CREATE_PRIVATE_THREADS", - readableName: "Create private threads", - description: "Allows the user to create private threads", - }, - { - name: "USE_EXTERNAL_STICKERS", - readableName: "Use external stickers", - description: "Allows user to use external stickers", - }, - { - name: "SEND_MESSAGES_IN_THREADS", - readableName: "Send messages in threads", - description: "Allows the user to send messages in threads", - }, - { - name: "USE_EMBEDDED_ACTIVITIES", - readableName: "Use activities", - description: "Allows the user to use embedded activities", - }, - { - name: "MODERATE_MEMBERS", - readableName: "Timeout members", - description: -"Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", - }, - { - name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", - readableName: "View creator monetization analytics", - description: "Allows for viewing role subscription insights", - }, - { - name: "USE_SOUNDBOARD", - readableName: "Use soundboard", - description: "Allows for using soundboard in a voice channel", - }, - { - name: "CREATE_GUILD_EXPRESSIONS", - readableName: "Create expressions", - description: -"Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", - }, - { - name: "CREATE_EVENTS", - readableName: "Create events", - description: -"Allows for creating scheduled events, and editing and deleting those created by the current user.", - }, - { - name: "USE_EXTERNAL_SOUNDS", - readableName: "Use external sounds", - description: -"Allows the usage of custom soundboard sounds from other servers", - }, - { - name: "SEND_VOICE_MESSAGES", - readableName: "Send voice messages", - description: "Allows sending voice messages", - }, - { - name: "SEND_POLLS", - readableName: "Create polls", - description: "Allows sending polls", - }, - { - name: "USE_EXTERNAL_APPS", - readableName: "Use external apps", - description: -"Allows user-installed apps to send public responses. " + -"When disabled, users will still be allowed to use their apps but the responses will be ephemeral. " + -"This only applies to apps not also installed to the server.", - }, - ]; - Permissions.map = {}; - let i = 0; - for(const thing of Permissions.info){ - Permissions.map[i] = thing; - Permissions.map[thing.name] = i; - i++; + //private static info: { name: string; readableName: string; description: string }[]; + static *info():Generator<{ name: string; readableName: string; description: string }>{ + for(const thing of this.permisions){ + yield { + name:thing, + readableName:I18n.getTranslation("permissions.readableNames."+thing), + description:I18n.getTranslation("permissions.descriptions."+thing), + } } } + static permisions=[ + "CREATE_INSTANT_INVITE", + "KICK_MEMBERS", + "BAN_MEMBERS", + "ADMINISTRATOR", + "MANAGE_CHANNELS", + "MANAGE_GUILD", + "ADD_REACTIONS", + "VIEW_AUDIT_LOG", + "PRIORITY_SPEAKER", + "STREAM", + "VIEW_CHANNEL", + "SEND_MESSAGES", + "SEND_TTS_MESSAGES", + "MANAGE_MESSAGES", + "EMBED_LINKS", + "ATTACH_FILES", + "READ_MESSAGE_HISTORY", + "MENTION_EVERYONE", + "USE_EXTERNAL_EMOJIS", + "VIEW_GUILD_INSIGHTS", + "CONNECT", + "SPEAK", + "MUTE_MEMBERS", + "DEAFEN_MEMBERS", + "MOVE_MEMBERS", + "USE_VAD", + "CHANGE_NICKNAME", + "MANAGE_NICKNAMES", + "MANAGE_ROLES", + "MANAGE_WEBHOOKS", + "MANAGE_GUILD_EXPRESSIONS", + "USE_APPLICATION_COMMANDS", + "REQUEST_TO_SPEAK", + "MANAGE_EVENTS", + "MANAGE_THREADS", + "CREATE_PUBLIC_THREADS", + "CREATE_PRIVATE_THREADS", + "USE_EXTERNAL_STICKERS", + "SEND_MESSAGES_IN_THREADS", + "USE_EMBEDDED_ACTIVITIES", + "MODERATE_MEMBERS", + "VIEW_CREATOR_MONETIZATION_ANALYTICS", + "USE_SOUNDBOARD", + "CREATE_GUILD_EXPRESSIONS", + "CREATE_EVENTS", + "USE_EXTERNAL_SOUNDS", + "SEND_VOICE_MESSAGES", + "SEND_POLLS", + "USE_EXTERNAL_APPS" + ]; getPermission(name: string): number{ - if(undefined===Permissions.map[name]){ - console.error(name +" is not found in map",Permissions.map); + if(undefined===Permissions.permisions.indexOf(name)){ + console.error(name +" is not found in map",Permissions.permisions); } - if(this.getPermissionbit(Permissions.map[name] as number, this.allow)){ + if(this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.allow)){ return 1; }else if( - this.getPermissionbit(Permissions.map[name] as number, this.deny) + this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.deny) ){ return-1; }else{ @@ -315,13 +105,13 @@ class Permissions{ "This function may of been used in error, think about using getPermision instead" ); } - if(this.getPermissionbit(Permissions.map[name] as number, this.allow)) + if(this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.allow)) return true; - if(name != "ADMINISTRATOR"&&adminOverride)return this.hasPermission("ADMINISTRATOR"); + if(name !== "ADMINISTRATOR"&&adminOverride)return this.hasPermission("ADMINISTRATOR"); return false; } setPermission(name: string, setto: number): void{ - const bit = Permissions.map[name] as number; + const bit = Permissions.permisions.indexOf(name) as number; if(bit===undefined){ return console.error( "Tried to set permission to " + @@ -346,5 +136,4 @@ name + } } } -Permissions.makeMap(); export{ Permissions }; diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 7b096e2d..b0256dc0 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -170,7 +170,7 @@ class RoleList extends Buttons{ this.permission = new Permissions("0"); } this.makeguildmenus(options); - for(const thing of Permissions.info){ + for(const thing of Permissions.info()){ options.options.push( new PermissionToggle(thing, this.permission, options) ); diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index 8f0fd4de..e5b1caa7 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -10,7 +10,111 @@ "en": { "reply": "Reply", "copyrawtext":"Copy raw text", - "copymessageid":"Copy message id" + "copymessageid":"Copy message id", + "permissions":{ + "descriptions":{ + "CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild", + "KICK_MEMBERS": "Allows the user to kick members from the guild", + "BAN_MEMBERS": "Allows the user to ban members from the guild", + "ADMINISTRATOR": "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", + "MANAGE_CHANNELS": "Allows the user to manage and edit channels", + "MANAGE_GUILD": "Allows management and editing of the guild", + "ADD_REACTIONS": "Allows user to add reactions to messages", + "VIEW_AUDIT_LOG": "Allows the user to view the audit log", + "PRIORITY_SPEAKER": "Allows for using priority speaker in a voice channel", + "STREAM": "Allows the user to stream", + "VIEW_CHANNEL": "Allows the user to view the channel", + "SEND_MESSAGES": "Allows user to send messages", + "SEND_TTS_MESSAGES": "Allows the user to send text-to-speech messages", + "MANAGE_MESSAGES": "Allows the user to delete messages that aren't their own", + "EMBED_LINKS": "Allow links sent by this user to auto-embed", + "ATTACH_FILES": "Allows the user to attach files", + "READ_MESSAGE_HISTORY": "Allows user to read the message history", + "MENTION_EVERYONE": "Allows the user to mention everyone", + "USE_EXTERNAL_EMOJIS": "Allows the user to use external emojis", + "VIEW_GUILD_INSIGHTS": "Allows the user to see guild insights", + "CONNECT": "Allows the user to connect to a voice channel", + "SPEAK": "Allows the user to speak in a voice channel", + "MUTE_MEMBERS": "Allows user to mute other members", + "DEAFEN_MEMBERS": "Allows user to deafen other members", + "MOVE_MEMBERS": "Allows the user to move members between voice channels", + "USE_VAD": "Allows users to speak in a voice channel by simply talking", + "CHANGE_NICKNAME": "Allows the user to change their own nickname", + "MANAGE_NICKNAMES": "Allows user to change nicknames of other members", + "MANAGE_ROLES": "Allows user to edit and manage roles", + "MANAGE_WEBHOOKS": "Allows management and editing of webhooks", + "MANAGE_GUILD_EXPRESSIONS": "Allows for managing emoji, stickers, and soundboards", + "USE_APPLICATION_COMMANDS": "Allows the user to use application commands", + "REQUEST_TO_SPEAK": "Allows user to request to speak in stage channel", + "MANAGE_EVENTS": "Allows user to edit and manage events", + "MANAGE_THREADS": "Allows the user to delete and archive threads and view all private threads", + "CREATE_PUBLIC_THREADS": "Allows the user to create public threads", + "CREATE_PRIVATE_THREADS": "Allows the user to create private threads", + "USE_EXTERNAL_STICKERS": "Allows user to use external stickers", + "SEND_MESSAGES_IN_THREADS": "Allows the user to send messages in threads", + "USE_EMBEDDED_ACTIVITIES": "Allows the user to use embedded activities", + "MODERATE_MEMBERS": "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Allows for viewing role subscription insights", + "USE_SOUNDBOARD": "Allows for using soundboard in a voice channel", + "CREATE_GUILD_EXPRESSIONS": "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", + "CREATE_EVENTS": "Allows for creating scheduled events, and editing and deleting those created by the current user.", + "USE_EXTERNAL_SOUNDS": "Allows the usage of custom soundboard sounds from other servers", + "SEND_VOICE_MESSAGES": "Allows sending voice messages", + "SEND_POLLS": "Allows sending polls", + "USE_EXTERNAL_APPS": "Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server." + }, + "readableNames":{ + "CREATE_INSTANT_INVITE": "Create invite", + "KICK_MEMBERS": "Kick members", + "BAN_MEMBERS": "Ban members", + "ADMINISTRATOR": "Administrator", + "MANAGE_CHANNELS": "Manage channels", + "MANAGE_GUILD": "Manage guild", + "ADD_REACTIONS": "Add reactions", + "VIEW_AUDIT_LOG": "View audit log", + "PRIORITY_SPEAKER": "Priority speaker", + "STREAM": "Video", + "VIEW_CHANNEL": "View channels", + "SEND_MESSAGES": "Send messages", + "SEND_TTS_MESSAGES": "Send text-to-speech messages", + "MANAGE_MESSAGES": "Manage messages", + "EMBED_LINKS": "Embed links", + "ATTACH_FILES": "Attach files", + "READ_MESSAGE_HISTORY": "Read message history", + "MENTION_EVERYONE": "Mention @everyone, @here and all roles", + "USE_EXTERNAL_EMOJIS": "Use external emojis", + "VIEW_GUILD_INSIGHTS": "View guild insights", + "CONNECT": "Connect", + "SPEAK": "Speak", + "MUTE_MEMBERS": "Mute members", + "DEAFEN_MEMBERS": "Deafen members", + "MOVE_MEMBERS": "Move members", + "USE_VAD": "Use voice activity detection", + "CHANGE_NICKNAME": "Change nickname", + "MANAGE_NICKNAMES": "Manage nicknames", + "MANAGE_ROLES": "Manage roles", + "MANAGE_WEBHOOKS": "Manage webhooks", + "MANAGE_GUILD_EXPRESSIONS": "Manage expressions", + "USE_APPLICATION_COMMANDS": "Use application commands", + "REQUEST_TO_SPEAK": "Request to speak", + "MANAGE_EVENTS": "Manage events", + "MANAGE_THREADS": "Manage threads", + "CREATE_PUBLIC_THREADS": "Create public threads", + "CREATE_PRIVATE_THREADS": "Create private threads", + "USE_EXTERNAL_STICKERS": "Use external stickers", + "SEND_MESSAGES_IN_THREADS": "Send messages in threads", + "USE_EMBEDDED_ACTIVITIES": "Use activities", + "MODERATE_MEMBERS": "Timeout members", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "View creator monetization analytics", + "USE_SOUNDBOARD": "Use soundboard", + "CREATE_GUILD_EXPRESSIONS": "Create expressions", + "CREATE_EVENTS": "Create events", + "USE_EXTERNAL_SOUNDS": "Use external sounds", + "SEND_VOICE_MESSAGES": "Send voice messages", + "SEND_POLLS": "Create polls", + "USE_EXTERNAL_APPS": "Use external apps" + } + } }, "ru": "./ru.json" } From dac1f55a7dadb482dc35421ab71477bd64ed3e69 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 20:57:05 -0500 Subject: [PATCH 0015/1330] get rid of redundent stuff --- src/webpage/permissions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webpage/permissions.ts b/src/webpage/permissions.ts index cd05d528..6f3f6643 100644 --- a/src/webpage/permissions.ts +++ b/src/webpage/permissions.ts @@ -89,10 +89,10 @@ class Permissions{ if(undefined===Permissions.permisions.indexOf(name)){ console.error(name +" is not found in map",Permissions.permisions); } - if(this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.allow)){ + if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)){ return 1; }else if( - this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.deny) + this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny) ){ return-1; }else{ @@ -105,13 +105,13 @@ class Permissions{ "This function may of been used in error, think about using getPermision instead" ); } - if(this.getPermissionbit(Permissions.permisions.indexOf(name) as number, this.allow)) + if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) return true; if(name !== "ADMINISTRATOR"&&adminOverride)return this.hasPermission("ADMINISTRATOR"); return false; } setPermission(name: string, setto: number): void{ - const bit = Permissions.permisions.indexOf(name) as number; + const bit = Permissions.permisions.indexOf(name); if(bit===undefined){ return console.error( "Tried to set permission to " + From 602b16a0efc170a99dbaa27009bbe9037b4912db Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 21:14:40 -0500 Subject: [PATCH 0016/1330] finish hooking the message into translation --- src/webpage/i18n.ts | 17 +++++++++-------- src/webpage/message.ts | 17 ++++++++--------- src/webpage/translations/en.json | 10 +++++++++- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index c30ad7b6..a7c3052f 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -57,6 +57,14 @@ class I18n{ } static fillInBlanks(msg:string,params:string[]):string{ //thanks to geotale for the regex + msg=msg.replace(/\$\d+/g,(match) => { + const number=Number(match.slice(1)); + if(params[number-1]){ + return params[number-1]; + }else{ + return match; + } + }); msg=msg.replace(/{{(.+?)}}/g, (str, match:string) => { const [op,strsSplit]=this.fillInBlanks(match,params).split(":"); @@ -86,14 +94,7 @@ class I18n{ return str; } ); - msg=msg.replace(/\$\d+/g,(str, match:string) => { - const number=Number(match); - if(params[number-1]){ - return params[number-1]; - }else{ - return str; - } - }); + return msg; } private static async toTranslation(trans:string|translation,lang:string):Promise{ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 3ab5480b..49b2acdb 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -341,8 +341,7 @@ class Message extends SnowFlake{ if(ignoredblock){ if(premessage?.author !== this.author){ const span = document.createElement("span"); - span.textContent = - "You have this user blocked, click to hide these messages."; + span.textContent = I18n.getTranslation("hideBlockedMessages"); div.append(span); span.classList.add("blocked"); span.onclick = _=>{ @@ -379,7 +378,7 @@ class Message extends SnowFlake{ this.channel.idToNext.get(next.id) as string ); } - span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; + span.textContent = I18n.getTranslation("showBlockedMessages",count+""); build.append(span); span.onclick = _=>{ const scroll = this.channel.infinite.scrollTop; @@ -595,13 +594,13 @@ class Message extends SnowFlake{ } const diaolog = new Dialog([ "vdiv", - ["title", "Are you sure you want to delete this?"], + ["title", I18n.getTranslation("deleteConfirm")], [ "hdiv", [ "button", "", - "Yes", + I18n.getTranslation("yes"), ()=>{ this.delete(); diaolog.hide(); @@ -610,7 +609,7 @@ class Message extends SnowFlake{ [ "button", "", - "No", + I18n.getTranslation("no"), ()=>{ diaolog.hide(); }, @@ -750,11 +749,11 @@ function formatTime(date: Date){ const formatTime = (date: Date)=>date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); if(datestring === now){ - return`Today at ${formatTime(date)}`; + return I18n.getTranslation("todayAt",formatTime(date)); }else if(datestring === yesterdayStr){ - return`Yesterday at ${formatTime(date)}`; + return I18n.getTranslation("yesterdayAt",formatTime(date)); }else{ - return`${date.toLocaleDateString()} at ${formatTime(date)}`; + return I18n.getTranslation("otherAt",formatTime(date),date.toLocaleDateString(),formatTime(date)); } } let tomorrow = 0; diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index e5b1caa7..26a351fb 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -114,7 +114,15 @@ "SEND_POLLS": "Create polls", "USE_EXTERNAL_APPS": "Use external apps" } - } + }, + "hideBlockedMessages":"You have this user blocked, click to hide these messages.", + "showBlockedMessages":"You have this user blocked, click to see the $1 blocked {{PLURAL:$1|message|messages}}.", + "deleteConfirm":"Are you sure you want to delete this?", + "yes":"Yes", + "no":"No", + "todayAt":"Today at $1", + "yesterdayAt":"Yesterday at $1", + "otherAt":"$1 at $2" }, "ru": "./ru.json" } From a0d870c1b36e105f636eeead905116ded3ccd9cb Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 22:58:37 -0500 Subject: [PATCH 0017/1330] push current translation support progress --- src/webpage/bot.ts | 21 +++-- src/webpage/channel.ts | 155 ++++++++++++------------------- src/webpage/direct.ts | 19 ++-- src/webpage/embed.ts | 10 +- src/webpage/emoji.ts | 9 +- src/webpage/file.ts | 4 +- src/webpage/guild.ts | 69 +++++++------- src/webpage/home.ts | 12 ++- src/webpage/i18n.ts | 1 + src/webpage/index.ts | 28 ++---- src/webpage/localuser.ts | 1 - src/webpage/message.ts | 6 +- src/webpage/settings.ts | 7 +- src/webpage/translations/en.json | 96 ++++++++++++++++++- 14 files changed, 243 insertions(+), 195 deletions(-) diff --git a/src/webpage/bot.ts b/src/webpage/bot.ts index 64cf641e..9d85e4b0 100644 --- a/src/webpage/bot.ts +++ b/src/webpage/bot.ts @@ -6,6 +6,7 @@ import { User } from "./user.js"; import {guildjson} from "./jsontypes.js"; import { PermissionToggle } from "./role.js"; import { Permissions } from "./permissions.js"; +import { I18n } from "./i18n.js"; class Bot{ readonly owner:Localuser; readonly token:string; @@ -27,7 +28,7 @@ class Bot{ }; } settings(){ - const settings = new Settings("Bot Settings"); + const settings = new Settings(I18n.getTranslation("botSettings")); const botOptions = settings.addButton("Profile",{ltr:true}); const bot=new User(this.json,this.localuser); { @@ -50,7 +51,7 @@ class Bot{ settingsRight.addHTMLArea(hypotheticalProfile); const finput = settingsLeft.addFileInput( - "Upload pfp:", + I18n.getTranslation("uploadPfp"), _=>{ if(file){ this.updatepfp(file); @@ -76,7 +77,7 @@ class Bot{ }); let bfile: undefined | File | null; const binput = settingsLeft.addFileInput( - "Upload banner:", + I18n.getTranslation("uploadBanner"), _=>{ if(bfile !== undefined){ this.updatebanner(bfile); @@ -102,7 +103,7 @@ class Bot{ }); let changed = false; const pronounbox = settingsLeft.addTextInput( - "Pronouns", + I18n.getTranslation("pronouns"), _=>{ if(newpronouns || newbio || changed){ this.updateProfile({ @@ -119,7 +120,7 @@ class Bot{ newpronouns = _; regen(); }); - const bioBox = settingsLeft.addMDInput("Bio:", _=>{}, { + const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, { initText: bot.bio.rawString, }); bioBox.watchForChange(_=>{ @@ -134,7 +135,7 @@ class Bot{ color = "transparent"; } const colorPicker = settingsLeft.addColorInput( - "Profile color", + I18n.getTranslation("profileColor"), _=>{}, { initColor: color } ); @@ -148,7 +149,7 @@ class Bot{ } { const guildsettings=settings.addButton("Guilds"); - guildsettings.addTitle("Guilds bot is in:"); + guildsettings.addTitle(I18n.getTranslation("botGuilds")); fetch(this.info.api+"/users/@me/guilds/",{ headers:this.headers }).then(_=>_.json()).then((json:(guildjson["properties"])[])=>{ @@ -187,8 +188,8 @@ class Bot{ content.onclick=()=>{ const guildsetting=guildsettings.addSubOptions(guild.name); guildsetting.addHTMLArea(content); - guildsetting.addButtonInput("","Leave Guild",()=>{ - if(confirm(`Are you sure you want to leave ${guild.name}?`)){ + guildsetting.addButtonInput("",I18n.getTranslation("leaveGuild"),()=>{ + if(confirm(I18n.getTranslation("confirmGuildLeave",guild.name))){ fetch(this.info.api+"/users/@me/guilds/"+guild.id,{ method:"DELETE", headers:this.headers @@ -250,7 +251,7 @@ class Bot{ }); } static InviteMaker(id:string,container:Form,info:Localuser["info"]){ - const gen=container.addSubOptions("URL generator",{ + const gen=container.addSubOptions(I18n.getTranslation("UrlGen"),{ noSubmit:true }); const params = new URLSearchParams(""); diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 227bfc8b..21b0bbcd 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -15,6 +15,7 @@ import{ MarkDown }from"./markdown.js"; import{ Member }from"./member.js"; import { Voice } from "./voice.js"; import { User } from "./user.js"; +import { I18n } from "./i18n.js"; declare global { interface NotificationOptions { @@ -53,20 +54,20 @@ class Channel extends SnowFlake{ voice?:Voice; bitrate:number=128000; static setupcontextmenu(){ - this.contextmenu.addbutton("Copy channel id", function(this: Channel){ + this.contextmenu.addbutton(()=>I18n.getTranslation("channel.copyId"), function(this: Channel){ navigator.clipboard.writeText(this.id); }); - this.contextmenu.addbutton("Mark as read", function(this: Channel){ + this.contextmenu.addbutton(()=>I18n.getTranslation("channel.markRead"), function(this: Channel){ this.readbottom(); }); - this.contextmenu.addbutton("Settings", function(this: Channel){ + this.contextmenu.addbutton(()=>I18n.getTranslation("channel.settings"), function(this: Channel){ this.generateSettings(); }); this.contextmenu.addbutton( - "Delete channel", + ()=>I18n.getTranslation("channel.delete"), function(this: Channel){ this.deleteChannel(); }, @@ -77,7 +78,7 @@ class Channel extends SnowFlake{ ); this.contextmenu.addbutton( - "Make invite", + ()=>I18n.getTranslation("channel.makeInvite"), function(this: Channel){ this.createInvite(); }, @@ -86,24 +87,6 @@ class Channel extends SnowFlake{ return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; } ); - /* - this.contextmenu.addbutton("Test button",function(){ - this.localuser.ws.send(JSON.stringify({ - "op": 14, - "d": { - "guild_id": this.guild.id, - "channels": { - [this.id]: [ - [ - 0, - 99 - ] - ] - } - } - })) - },null); - /**/ } createInvite(){ const div = document.createElement("div"); @@ -148,20 +131,20 @@ class Channel extends SnowFlake{ update(); new Dialog([ "vdiv", - ["title", "Invite people"], + ["title", I18n.getTranslation("inviteOptions.title")], ["text", `to #${this.name} in ${this.guild.properties.name}`], [ "select", "Expire after:", [ - "30 Minutes", - "1 Hour", - "6 Hours", - "12 Hours", - "1 Day", - "7 Days", - "30 Days", - "Never", + I18n.getTranslation("inviteOptions.30m"), + I18n.getTranslation("inviteOptions.1h"), + I18n.getTranslation("inviteOptions.6h"), + I18n.getTranslation("inviteOptions.12h"), + I18n.getTranslation("inviteOptions.1d"), + I18n.getTranslation("inviteOptions.7d"), + I18n.getTranslation("inviteOptions.30d"), + I18n.getTranslation("inviteOptions.never"), ], function(e: Event){ expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0,][(e.srcElement as HTMLSelectElement).selectedIndex]; @@ -174,13 +157,13 @@ class Channel extends SnowFlake{ "select", "Max uses:", [ - "No limit", - "1 use", - "5 uses", - "10 uses", - "25 uses", - "50 uses", - "100 uses", + I18n.getTranslation("inviteOptions.noLimit"), + I18n.getTranslation("inviteOptions.limit","1"), + I18n.getTranslation("inviteOptions.limit","5"), + I18n.getTranslation("inviteOptions.limit","10"), + I18n.getTranslation("inviteOptions.limit","25"), + I18n.getTranslation("inviteOptions.limit","50"), + I18n.getTranslation("inviteOptions.limit","100"), ], function(e: Event){ uses = [0, 1, 5, 10, 25, 50, 100][(e.srcElement as HTMLSelectElement).selectedIndex]; @@ -193,7 +176,7 @@ class Channel extends SnowFlake{ } generateSettings(){ this.sortPerms(); - const settings = new Settings("Settings for " + this.name); + const settings = new Settings(I18n.getTranslation("channel.settingsFor",this.name)); { const gensettings=settings.addButton("Settings"); const form=gensettings.addForm("",()=>{},{ @@ -201,18 +184,19 @@ class Channel extends SnowFlake{ 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}); + form.addTextInput(I18n.getTranslation("channel.name:"),"name",{initText:this.name}); + form.addMDInput(I18n.getTranslation("channel.topic:"),"topic",{initText:this.topic}); + form.addCheckboxInput(I18n.getTranslation("channel.nsfw:"),"nsfw",{initState:this.nsfw}); if(this.type!==4){ const options=["voice", "text", "announcement"]; - form.addSelect("Type:","type",options,{ + form.addSelect("Type:","type",options.map(e=>I18n.getTranslation("channel."+e)),{ defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string) + },options); + form.addPreprocessor((obj:any)=>{ + obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.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("Permissions"); s1.options.push( @@ -308,10 +292,7 @@ class Channel extends SnowFlake{ 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; } if(!this.permission_overwrites.has(thing.id)){ @@ -376,10 +357,10 @@ class Channel extends SnowFlake{ } return( Boolean(this.lastmessageid) && - (!this.lastreadmessageid || - SnowFlake.stringToUnixTime(this.lastmessageid as string) > - SnowFlake.stringToUnixTime(this.lastreadmessageid)) && - this.type !== 4 + (!this.lastreadmessageid || + SnowFlake.stringToUnixTime(this.lastmessageid as string) > + SnowFlake.stringToUnixTime(this.lastreadmessageid)) && + this.type !== 4 ); } hasPermission(name: string, member = this.guild.member): boolean{ @@ -406,10 +387,7 @@ class Channel extends SnowFlake{ return false; } get canMessage(): boolean{ - if( - this.permission_overwritesar.length === 0 && - this.hasPermission("MANAGE_CHANNELS") - ){ + if(this.permission_overwritesar.length === 0 &&this.hasPermission("MANAGE_CHANNELS")){ const role = this.guild.roles.find(_=>_.name === "@everyone"); if(role){ this.addRoleToPerms(role); @@ -435,16 +413,17 @@ class Channel extends SnowFlake{ calculateReorder(){ let position = -1; const build: { - id: string; - position: number | undefined; - parent_id: string | undefined; - }[] = []; + id: string; + position: number | undefined; + parent_id: string | undefined; + }[] = []; for(const thing of this.children){ const thisthing: { - id: string; - position: number | undefined; - parent_id: string | undefined; - } = { id: thing.id, position: undefined, parent_id: undefined }; + id: string; + position: number | undefined; + parent_id: string | undefined; + } = { id: thing.id, position: undefined, parent_id: undefined }; + if(thing.position < position){ thing.position = thisthing.position = position + 1; } @@ -652,12 +631,7 @@ class Channel extends SnowFlake{ return; } fetch( - this.info.api + - "/channels/" + - this.id + - "/messages/" + - this.lastmessageid + - "/ack", + this.info.api +"/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", { method: "POST", headers: this.headers, @@ -768,7 +742,7 @@ class Channel extends SnowFlake{ if(this.replyingto){ replybox.innerHTML = ""; const span = document.createElement("span"); - span.textContent = "Replying to " + this.replyingto.author.username; + span.textContent = I18n.getTranslation("replyingTo", this.replyingto.author.username); const X = document.createElement("button"); X.onclick = _=>{ if(this.replyingto?.div){ @@ -827,9 +801,7 @@ class Channel extends SnowFlake{ history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); this.localuser.pageTitle("#" + this.name); - const channelTopic = document.getElementById( - "channelTopic" - ) as HTMLSpanElement; + const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; if(this.topic){ channelTopic.innerHTML = new MarkDown( this.topic, @@ -855,8 +827,7 @@ class Channel extends SnowFlake{ await this.buildmessages(); //loading.classList.remove("loading"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = - "" + this.canMessage; + (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; } typingmap: Map = new Map(); async typingStart(typing: startTypingjson): Promise{ @@ -893,11 +864,7 @@ class Channel extends SnowFlake{ this.typingmap.delete(thing); } } - if(i > 1){ - build += " are typing"; - }else{ - build += " is typing"; - } + build=I18n.getTranslation("typing",i+"",build); if(this.localuser.channelfocus === this){ if(showing){ typingtext.classList.remove("hidden"); @@ -1013,12 +980,7 @@ class Channel extends SnowFlake{ } await fetch( - this.info.api + - "/channels/" + - this.id + - "/messages?before=" + - id + - "&limit=100", + this.info.api + "/channels/" + this.id +"/messages?before=" + id + "&limit=100", { headers: this.headers, } @@ -1059,10 +1021,10 @@ class Channel extends SnowFlake{ }); } /** - * Please dont use this, its not implemented. - * @deprecated - * @todo - **/ + * Please dont use this, its not implemented. + * @deprecated + * @todo + **/ async grabArround(/* id: string */){ //currently unused and no plans to use it yet throw new Error("please don't call this, no one has implemented it :P"); @@ -1099,8 +1061,7 @@ class Channel extends SnowFlake{ if(!removetitle){ const title = document.createElement("h2"); title.id = "removetitle"; - title.textContent = - "No messages appear to be here, be the first to say something!"; + title.textContent = I18n.getTranslation("noMessages"); title.classList.add("titlespace"); messages.append(title); } @@ -1353,7 +1314,7 @@ class Channel extends SnowFlake{ noticontent ||= message.embeds[0]?.json.title; noticontent ||= message.content.textContent; } - noticontent ||= "Blank Message"; + noticontent ||= I18n.getTranslation("blankMessage"); let imgurl: null | string = null; const images = message.getimages(); if(images.length){ diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index edabcd51..85685749 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -12,6 +12,7 @@ import{ import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{ Contextmenu }from"./contextmenu.js"; +import { I18n } from "./i18n.js"; class Direct extends Guild{ declare channelids: { [key: string]: Group }; @@ -99,24 +100,24 @@ dmPermissions.setPermission("SPEAK", 1); dmPermissions.setPermission("STREAM", 1); dmPermissions.setPermission("USE_VAD", 1); -// @ts-ignore +// @ts-ignore I need to look into this lol class Group extends Channel{ user: User; static contextmenu = new Contextmenu("channel menu"); static setupcontextmenu(){ - this.contextmenu.addbutton("Copy DM id", function(this: Group){ + this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.copyId"), function(this: Group){ navigator.clipboard.writeText(this.id); }); - this.contextmenu.addbutton("Mark as read", function(this: Group){ + this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.markRead"), function(this: Group){ this.readbottom(); }); - this.contextmenu.addbutton("Close DM", function(this: Group){ + this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.close"), function(this: Group){ this.deleteChannel(); }); - this.contextmenu.addbutton("Copy user ID", function(){ + this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(){ navigator.clipboard.writeText(this.user.id); }); } @@ -179,10 +180,7 @@ class Group extends Channel{ const prom = this.infinite.delete(); history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); this.localuser.pageTitle("@" + this.name); - (document.getElementById("channelTopic") as HTMLElement).setAttribute( - "hidden", - "" - ); + (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); const loading = document.getElementById("loadingdiv") as HTMLDivElement; Channel.regenLoadingMessages(); @@ -306,4 +304,5 @@ class Group extends Channel{ } } export{ Direct, Group }; -Group.setupcontextmenu(); + +Group.setupcontextmenu() diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index 91804fc3..d2f8fe92 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -4,6 +4,7 @@ import{ MarkDown }from"./markdown.js"; import{ embedjson, invitejson }from"./jsontypes.js"; import{ getapiurls, getInstances }from"./login.js"; import{ Guild }from"./guild.js"; +import { I18n } from "./i18n.js"; class Embed{ type: string; @@ -296,8 +297,7 @@ guild as invitejson["guild"] & { info: { cdn: string } } guildinfo.append(name); const members = document.createElement("span"); - members.innerText = -"#" + json.channel.name + " • Members: " + guild.member_count; + members.innerText = "#" + json.channel.name + " • Members: " + guild.member_count; guildinfo.append(members); members.classList.add("subtext"); iconrow.append(guildinfo); @@ -305,12 +305,12 @@ guild as invitejson["guild"] & { info: { cdn: string } } div.append(iconrow); const h2 = document.createElement("h2"); - h2.textContent = `You've been invited by ${json.inviter.username}`; + h2.textContent = I18n.getTranslation("invite.invitedBy",json.inviter.username); div.append(h2); const button = document.createElement("button"); - button.textContent = "Accept"; + button.textContent = I18n.getTranslation("invite.accept"); if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){ - button.textContent = "Already joined"; + button.textContent = I18n.getTranslation("invite.alreadyJoined"); button.disabled = true; } button.classList.add("acceptinvbutton"); diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index c605345f..7380cd8e 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -2,6 +2,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; +//I need to recompile the emoji format for translation class Emoji{ static emojis: { name: string; @@ -158,13 +159,7 @@ class Emoji{ const img = document.createElement("img"); img.classList.add("pfp", "servericon", "emoji-server"); img.crossOrigin = "anonymous"; - img.src = - localuser.info.cdn + - "/icons/" + - guild.properties.id + - "/" + - guild.properties.icon + - ".png?size=48"; + img.src = localuser.info.cdn+"/icons/"+guild.properties.id+"/"+guild.properties.icon+".png?size=48"; img.alt = "Server: " + guild.properties.name; select.appendChild(img); }else{ diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 63969ca0..3a67853a 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -145,9 +145,7 @@ class File{ static filesizehuman(fsize: number){ const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); return( - Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + -" " + -["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] + Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong ); } } diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 22b16e79..dd6091d4 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -16,6 +16,7 @@ import{ rolesjson, }from"./jsontypes.js"; import{ User }from"./user.js"; +import { I18n } from "./i18n.js"; class Guild extends SnowFlake{ owner!: Localuser; @@ -38,20 +39,20 @@ class Guild extends SnowFlake{ members=new Set(); static contextmenu = new Contextmenu("guild menu"); static setupcontextmenu(){ - Guild.contextmenu.addbutton("Copy Guild id", function(this: Guild){ + Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.copyId"), function(this: Guild){ navigator.clipboard.writeText(this.id); }); - Guild.contextmenu.addbutton("Mark as read", function(this: Guild){ + Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.markRead"), function(this: Guild){ this.markAsRead(); }); - Guild.contextmenu.addbutton("Notifications", function(this: Guild){ + Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.notifications"), function(this: Guild){ this.setnotifcation(); }); Guild.contextmenu.addbutton( - "Leave guild", + ()=>I18n.getTranslation("guild.leave"), function(this: Guild){ this.confirmleave(); }, @@ -62,7 +63,7 @@ class Guild extends SnowFlake{ ); Guild.contextmenu.addbutton( - "Delete guild", + ()=>I18n.getTranslation("guild.delete"), function(this: Guild){ this.confirmDelete(); }, @@ -73,13 +74,13 @@ class Guild extends SnowFlake{ ); Guild.contextmenu.addbutton( - "Create invite", + ()=>I18n.getTranslation("guild.makeInvite"), function(this: Guild){}, null, _=>true, _=>false ); - Guild.contextmenu.addbutton("Settings", function(this: Guild){ + Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.settings"), function(this: Guild){ this.generateSettings(); }); /* -----things left for later----- @@ -94,28 +95,28 @@ class Guild extends SnowFlake{ */ } generateSettings(){ - const settings = new Settings("Settings for " + this.properties.name); + const settings = new Settings(I18n.getTranslation("guild.settingsFor",this.properties.name)); { - const overview = settings.addButton("Overview"); + const overview = settings.addButton(I18n.getTranslation("guild.overview")); const form = overview.addForm("", _=>{}, { headers: this.headers, traditionalSubmit: true, fetchURL: this.info.api + "/guilds/" + this.id, method: "PATCH", }); - form.addTextInput("Name:", "name", { initText: this.properties.name }); + form.addTextInput(I18n.getTranslation("guild.name:"), "name", { initText: this.properties.name }); form.addMDInput("Description:", "description", { initText: this.properties.description, }); - form.addFileInput("Banner:", "banner", { clear: true }); - form.addFileInput("Icon:", "icon", { clear: true }); + form.addFileInput(I18n.getTranslation("guild.banner:"), "banner", { clear: true }); + form.addFileInput(I18n.getTranslation("guild.icon:"), "icon", { clear: true }); let region = this.properties.region; if(!region){ region = ""; } - form.addTextInput("Region:", "region", { initText: region }); + form.addTextInput(I18n.getTranslation("guild.region:"), "region", { initText: region }); } - const s1 = settings.addButton("Roles"); + const s1 = settings.addButton(I18n.getTranslation("guild.roles")); const permlist: [Role, Permissions][] = []; for(const thing of this.roles){ permlist.push([thing, thing.permissions]); @@ -261,14 +262,15 @@ class Guild extends SnowFlake{ } setnotifcation(){ let noti = this.message_notifications; + const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e)) const notiselect = new Dialog([ "vdiv", [ "radio", - "select notifications type", - ["all", "only mentions", "none"], - function(e: string /* "all" | "only mentions" | "none" */){ - noti = ["all", "only mentions", "none"].indexOf(e); + I18n.getTranslation("guild.selectnoti"), + options, + function(e: string){ + noti = options.indexOf(e); }, noti, ], @@ -294,13 +296,13 @@ class Guild extends SnowFlake{ confirmleave(){ const full = new Dialog([ "vdiv", - ["title", "Are you sure you want to leave?"], + ["title", I18n.getTranslation("guild.confirmLeave")], [ "hdiv", [ "button", "", - "Yes, I'm sure", + I18n.getTranslation("yesLeave"), (_: any)=>{ this.leave().then(_=>{ full.hide(); @@ -310,7 +312,7 @@ class Guild extends SnowFlake{ [ "button", "", - "Nevermind", + I18n.getTranslation("noLeave"), (_: any)=>{ full.hide(); }, @@ -467,11 +469,11 @@ class Guild extends SnowFlake{ "vdiv", [ "title", - "Are you sure you want to delete " + this.properties.name + "?", + I18n.getTranslation("guild.confirmDelete",this.properties.name) ], [ "textbox", - "Name of server:", + I18n.getTranslation("serverName"), "", function(this: HTMLInputElement){ confirmname = this.value; @@ -482,7 +484,7 @@ class Guild extends SnowFlake{ [ "button", "", - "Yes, I'm sure", + I18n.getTranslation("yesDelete"), (_: any)=>{ console.log(confirmname); if(confirmname !== this.properties.name){ @@ -496,7 +498,7 @@ class Guild extends SnowFlake{ [ "button", "", - "Nevermind", + I18n.getTranslation("noDelete"), (_: any)=>{ full.hide(); }, @@ -638,22 +640,23 @@ class Guild extends SnowFlake{ createchannels(func = this.createChannel){ let name = ""; let category = 0; + const options=["voice", "text", "announcement"].map(e=>I18n.getTranslation("channel."+e)); + const numbers=[2,0,5] const channelselect = new Dialog([ "vdiv", [ "radio", - "select channel type", - ["voice", "text", "announcement"], + I18n.getTranslation("channel.selectType"), + options, function(radio: string){ console.log(radio); - category = - { text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0; + category = numbers[options.indexOf(radio)] || 0; }, 1, ], [ "textbox", - "Name of channel", + I18n.getTranslation("channel.selectName"), "", function(this: HTMLInputElement){ name = this.value; @@ -662,7 +665,7 @@ class Guild extends SnowFlake{ [ "button", "", - "submit", + I18n.getTranslation("submit"), ()=>{ console.log(name, category); func.bind(this)(name, category); @@ -679,7 +682,7 @@ class Guild extends SnowFlake{ "vdiv", [ "textbox", - "Name of category", + I18n.getTranslation("channel.selectCatName"), "", function(this: HTMLInputElement){ name = this.value; @@ -688,7 +691,7 @@ class Guild extends SnowFlake{ [ "button", "", - "submit", + I18n.getTranslation("submit"), function(this:Guild){ console.log(name, category); this.createChannel(name, category); diff --git a/src/webpage/home.ts b/src/webpage/home.ts index ce94e44e..57c4824e 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -1,3 +1,4 @@ +import { I18n } from "./i18n.js"; import{ mobile }from"./login.js"; console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; @@ -5,7 +6,7 @@ const serverbox = document.getElementById("instancebox") as HTMLDivElement; fetch("/instances.json") .then(_=>_.json()) .then( - ( + async ( json: { name: string; description?: string; @@ -24,6 +25,7 @@ fetch("/instances.json") }; }[] )=>{ + await I18n.done; console.warn(json); for(const instance of json){ if(instance.display === false){ @@ -66,11 +68,11 @@ fetch("/instances.json") const stats = document.createElement("div"); stats.classList.add("flexltr"); const span = document.createElement("span"); - span.innerText = `Uptime: All time: ${Math.round( + span.innerText = I18n.getTranslation("home.uptimeStats",Math.round( instance.uptime.alltime * 100 - )}% This week: ${Math.round( + )+"",Math.round( instance.uptime.weektime * 100 - )}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; + )+"",Math.round(instance.uptime.daytime * 100)+"") stats.append(span); statbox.append(stats); } @@ -79,7 +81,7 @@ fetch("/instances.json") if(instance.online){ window.location.href = "/register.html?instance=" + encodeURI(instance.name); }else{ - alert("Instance is offline, can't connect"); + alert(I18n.getTranslation("home.warnOffiline")); } }; serverbox.append(div); diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index a7c3052f..09c8949f 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -105,4 +105,5 @@ class I18n{ } } } +I18n.create("/translations/en.json","en") export{I18n}; diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 7f19a017..1b40b9d8 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -4,6 +4,7 @@ import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js"; import{ MarkDown }from"./markdown.js"; import{ Message }from"./message.js"; import{ File }from"./file.js"; +import { I18n } from "./i18n.js"; (async ()=>{ async function waitForLoad(): Promise{ @@ -13,7 +14,7 @@ import{ File }from"./file.js"; } await waitForLoad(); - + await I18n.done const users = getBulkUsers(); if(!users.currentuser){ window.location.href = "/login.html"; @@ -74,7 +75,7 @@ import{ File }from"./file.js"; const switchAccountDiv = document.createElement("div"); switchAccountDiv.classList.add("switchtable"); - switchAccountDiv.textContent = "Switch accounts ⇌"; + switchAccountDiv.textContent = I18n.getTranslation("switchAccounts"); switchAccountDiv.addEventListener("click", ()=>{ window.location.href = "/login.html"; }); @@ -93,9 +94,7 @@ import{ File }from"./file.js"; showAccountSwitcher(); }); - const switchAccountsElement = document.getElementById( - "switchaccounts" - ) as HTMLDivElement; + const switchAccountsElement = document.getElementById("switchaccounts") as HTMLDivElement; switchAccountsElement.addEventListener("click", event=>{ event.stopImmediatePropagation(); showAccountSwitcher(); @@ -115,14 +114,13 @@ import{ File }from"./file.js"; }); }catch(e){ console.error(e); - (document.getElementById("load-desc") as HTMLSpanElement).textContent = - "Account unable to start"; + (document.getElementById("load-desc") as HTMLSpanElement).textContent = I18n.getTranslation("accountNotStart"); thisUser = new Localuser(-1); } - const menu = new Contextmenu("create rightclick"); + const menu = new Contextmenu("create rightclick"); menu.addbutton( - "Create channel", + I18n.getTranslation("channel.createChannel"), ()=>{ if(thisUser.lookingguild){ thisUser.lookingguild.createchannels(); @@ -133,7 +131,7 @@ import{ File }from"./file.js"; ); menu.addbutton( - "Create category", + I18n.getTranslation("channel.createCatagory"), ()=>{ if(thisUser.lookingguild){ thisUser.lookingguild.createcategory(); @@ -143,15 +141,9 @@ import{ File }from"./file.js"; ()=>thisUser.isAdmin() ); - menu.bindContextmenu( - document.getElementById("channels") as HTMLDivElement, - 0, - 0 - ); + menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement); - const pasteImageElement = document.getElementById( - "pasteimage" - ) as HTMLDivElement; + const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement; let replyingTo: Message | null = null; async function handleEnter(event: KeyboardEvent): Promise{ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 589ff7c4..17cc1842 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -65,7 +65,6 @@ class Localuser{ "Content-type": "application/json; charset=UTF-8", Authorization: this.userinfo.token, }; - I18n.create("/translations/en.json","en") } async gottenReady(ready: readyjson): Promise{ await I18n.done; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 49b2acdb..574a106b 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -56,13 +56,13 @@ class Message extends SnowFlake{ Message.setupcmenu(); } static setupcmenu(){ - Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copyrawtext"), function(this: Message){ + Message.contextmenu.addbutton(()=>I18n.getTranslation("copyrawtext"), function(this: Message){ navigator.clipboard.writeText(this.content.rawString); }); - Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"reply"), function(this: Message){ + Message.contextmenu.addbutton(()=>I18n.getTranslation("reply"), function(this: Message){ this.channel.setReplying(this); }); - Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copymessageid"), function(this: Message){ + Message.contextmenu.addbutton(()=>I18n.getTranslation("copymessageid"), function(this: Message){ navigator.clipboard.writeText(this.id); }); Message.contextmenu.addsubmenu( diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 245948b1..8e7fd739 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -938,15 +938,18 @@ class Form implements OptionsElement{ (this.button.deref() as HTMLElement).hidden=false; } } + selectMap=new WeakMap(); addSelect( label: string, formName: string, selections: string[], - { defaultIndex = 0, required = false } = {} + { defaultIndex = 0, required = false}={}, + correct:string[]=selections ){ const select = this.options.addSelect(label, _=>{}, selections, { defaultIndex, }); + this.selectMap.set(select,correct); this.names.set(formName, select); if(required){ this.required.add(select); @@ -1110,7 +1113,7 @@ class Form implements OptionsElement{ if(thing === "")continue; const input = this.names.get(thing) as OptionsElement; if(input instanceof SelectInput){ - (build as any)[thing] = input.options[input.value]; + (build as any)[thing] = (this.selectMap.get(input) as string[])[input.value]; continue; }else if(input instanceof FileInput){ const options = this.fileOptions.get(input); diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index 26a351fb..7841732f 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -122,7 +122,101 @@ "no":"No", "todayAt":"Today at $1", "yesterdayAt":"Yesterday at $1", - "otherAt":"$1 at $2" + "otherAt":"$1 at $2", + "botSettings":"Bot Settings", + "uploadPfp":"Upload pfp:", + "uploadBanner":"Upload banner:", + "pronouns":"Pronouns:", + "bio":"Bio:", + "profileColor":"Profile color", + "botGuilds":"Guilds bot is in:", + "leaveGuild":"Leave Guild", + "confirmGuildLeave":"Are you sure you want to leave $1", + "UrlGen":"URL generator", + "typing":"$2 {{PLURAL:$1|are|is}} typing", + "noMessages":"No messages appear to be here, be the first to say something!", + "blankMessage":"Blank Message", + "channel":{ + "copyId":"Copy channel id", + "markRead":"Mark as read", + "settings":"Settings", + "delete":"Delete channel", + "makeInvite":"Make invite", + "settingsFor":"Settings for $1", + "voice":"Voice", + "text":"Text", + "announcement":"Announcements", + "name:":"Name:", + "topic:":"Topic:", + "nsfw:":"NSFW:", + "selectType":"Select channel type", + "selectName":"Name of channel", + "selectCatName":"Name of channel", + "createChannel":"Create channel", + "createCatagory":"Create category" + }, + "switchAccounts":"Switch accounts ⇌", + "accountNotStart":"Account unable to start", + "home":{ + "uptimeStats":"Uptime: \n All time: $1\nThis week: $2\nToday: $3", + "warnOffiline":"Instance is offline, can't connect" + }, + "submit":"submit", + "guild":{ + "copyId":"Copy guild id", + "markRead":"Mark as read", + "notifications":"Notifications", + "leave":"Leave guild", + "settings":"Settings", + "delete":"Delete guild", + "makeInvite":"Make invite", + "settingsFor":"Settings for $1", + "name:":"Name:", + "topic:":"Topic:", + "icon:":"Icon:", + "overview":"Overview", + "banner:":"Banner:", + "region:":"Region:", + "roles":"Roles", + "selectnoti":"Select notifications type", + "all":"all", + "onlyMentions":"only mentions", + "none":"node", + "confirmLeave":"Are you sure you want to leave?", + "yesLeave":"Yes, I'm sure", + "noLeave":"Nevermind", + "confirmDelete":"Are you sure you want to delete $1?", + "serverName":"Name of server:", + "yesDelete":"Yes, I'm sure", + "noDelete":"Nevermind" + }, + "inviteOptions":{ + "title":"Invite People", + "30m":"30 Minutes", + "1h":"1 Hour", + "6h":"6 Hours", + "12h":"12 Hours", + "1d":"1 Day", + "7d":"7 Days", + "30d":"30 Days", + "never":"Never", + "limit":"$1 {{PLURAL:$1|use|uses}}", + "noLimit":"No limit" + }, + "invite":{ + "invitedBy":"You've been invited by $1", + "alreadyJoined":"Already joined", + "accept":"Accept" + }, + "replyingTo":"Replying to $1", + "DMs":{ + "copyId":"Copy DM id", + "markRead":"Mark as read", + "close":"Close DM" + }, + "user":{ + "copyId":"Copy user ID" + } }, "ru": "./ru.json" } From 4e7d181a144e9dc9907feb1fe0313905307af10f Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 31 Oct 2024 23:54:17 -0500 Subject: [PATCH 0018/1330] more translation progress I'm done for the night, I need sleep lol --- src/webpage/i18n.ts | 1 + src/webpage/invite.ts | 10 +-- src/webpage/localuser.ts | 139 ++++++++++++++----------------- src/webpage/translations/en.json | 54 +++++++++++- 4 files changed, 118 insertions(+), 86 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 09c8949f..af21a882 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -38,6 +38,7 @@ class I18n{ for(const thing of path){ if(typeof jsont !== "string" ){ jsont=jsont[thing]; + }else{ jsont=json; break; diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index c285c603..15ebecaa 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -1,3 +1,4 @@ +import { I18n } from "./i18n.js"; import{ getBulkUsers, Specialuser, getapiurls }from"./login.js"; (async ()=>{ @@ -38,10 +39,9 @@ import{ getBulkUsers, Specialuser, getapiurls }from"./login.js"; }else{ urls = joinable[0].serverurls; } - + await I18n.done; if(!joinable.length){ -document.getElementById("AcceptInvite")!.textContent = -"Create an account to accept the invite"; +document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("noAccount"); } const code = window.location.pathname.split("/")[2]; @@ -57,7 +57,7 @@ document.getElementById("AcceptInvite")!.textContent = document.getElementById("invitename")!.textContent = guildjson.name; document.getElementById( "invitedescription" -)!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`; +)!.textContent = I18n.getTranslation("invite.longInvitedBy",json.inviter.username,guildjson.name) if(guildjson.icon){ const img = document.createElement("img"); img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; @@ -120,7 +120,7 @@ document.getElementById("inviteimg")!.append(div); const td = document.createElement("div"); td.classList.add("switchtable"); - td.textContent = "Login or create an account ⇌"; + td.textContent = I18n.getTranslation("invite.loginOrCreateAccount"); td.addEventListener("click", ()=>{ const l = new URLSearchParams("?"); l.set("goback", window.location.href); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 17cc1842..76a37aa6 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -284,10 +284,7 @@ class Localuser{ else this.errorBackoff++; this.connectionSucceed = 0; - (document.getElementById("load-desc") as HTMLElement).innerHTML = - "Unable to connect to the Spacebar server, retrying in " + - Math.round(0.2 + this.errorBackoff * 2.8) + - " seconds..."; + (document.getElementById("load-desc") as HTMLElement).innerHTML = I18n.getTranslation("errorReconnect",Math.round(0.2 + this.errorBackoff * 2.8)+"") switch( this.errorBackoff //try to recover from bad domain ){ @@ -331,8 +328,7 @@ class Localuser{ } setTimeout(()=>{ if(this.swapped)return; - (document.getElementById("load-desc") as HTMLElement).textContent = - "Retrying..."; + (document.getElementById("load-desc") as HTMLElement).textContent =I18n.getTranslation("retrying"); this.initwebsocket().then(()=>{ this.loaduser(); this.init(); @@ -343,10 +339,8 @@ class Localuser{ }); }, 200 + this.errorBackoff * 2800); }else - (document.getElementById("load-desc") as HTMLElement).textContent = - "Unable to connect to the Spacebar server. Please try logging out and back in."; + (document.getElementById("load-desc") as HTMLElement).textContent = I18n.getTranslation("unableToConnect") }); - await promise; } async handleEvent(temp: wsjson){ @@ -650,10 +644,10 @@ class Localuser{ category.classList.add("memberList"); let title=document.createElement("h3"); if(role==="offline"){ - title.textContent="Offline"; + title.textContent=I18n.getTranslation("user.offline"); category.classList.add("offline"); }else if(role==="online"){ - title.textContent="Online"; + title.textContent=I18n.getTranslation("user.online"); }else{ title.textContent=role.name; } @@ -734,12 +728,9 @@ class Localuser{ } } loaduser(): void{ - (document.getElementById("username") as HTMLSpanElement).textContent = - this.user.username; - (document.getElementById("userpfp") as HTMLImageElement).src = - this.user.getpfpsrc(); - (document.getElementById("status") as HTMLSpanElement).textContent = - this.status; + (document.getElementById("username") as HTMLSpanElement).textContent = this.user.username; + (document.getElementById("userpfp") as HTMLImageElement).src = this.user.getpfpsrc(); + (document.getElementById("status") as HTMLSpanElement).textContent = this.status; } isAdmin(): boolean{ if(this.lookingguild){ @@ -854,12 +845,12 @@ class Localuser{ "tabs", [ [ - "Join using invite", + I18n.getTranslation("invite.joinUsing"), [ "vdiv", [ "textbox", - "Invite Link/Code", + I18n.getTranslation("invite.inviteLinkCode"), "", function(this: HTMLInputElement){ inviteurl = this.value; @@ -869,7 +860,7 @@ class Localuser{ [ "button", "", - "Submit", + I18n.getTranslation("submit"), (_: any)=>{ let parsed = ""; if(inviteurl.includes("/")){ @@ -893,13 +884,13 @@ class Localuser{ ], ], [ - "Create Guild", + I18n.getTranslation("guild.create"), [ "vdiv", - ["title", "Create a guild"], + ["title", I18n.getTranslation("guild.create")], [ "fileupload", - "Icon: ", + I18n.getTranslation("guild.icon:"), function(event: Event){ const target = event.target as HTMLInputElement; if(!target.files)return; @@ -912,7 +903,7 @@ class Localuser{ ], [ "textbox", - "Name:", + I18n.getTranslation("guild.name:"), "", function(this: HTMLInputElement, event: Event){ const target = event.target as HTMLInputElement; @@ -922,7 +913,7 @@ class Localuser{ [ "button", "", - "Submit", + I18n.getTranslation("submit"), ()=>{ this.makeGuild(fields).then(_=>{ if(_.message){ @@ -951,7 +942,7 @@ class Localuser{ async guildDiscovery(){ const content = document.createElement("div"); content.classList.add("flexttb","guildy"); - content.textContent = "Loading..."; + content.textContent = I18n.getTranslation("guild.loadingDiscovery"); const full = new Dialog(["html", content]); full.show(); @@ -962,7 +953,7 @@ class Localuser{ content.innerHTML = ""; const title = document.createElement("h2"); - title.textContent = "Guild discovery (" + json.total + " entries)"; + title.textContent = I18n.getTranslation("guild.disoveryTitle"); content.appendChild(title); const guilds = document.createElement("div"); @@ -976,13 +967,7 @@ class Localuser{ const banner = document.createElement("img"); banner.classList.add("banner"); banner.crossOrigin = "anonymous"; - banner.src = - this.info.cdn + - "/icons/" + - guild.id + - "/" + - guild.banner + - ".png?size=256"; + banner.src =this.info.cdn +"/icons/" +guild.id +"/" +guild.banner +".png?size=256"; banner.alt = ""; content.appendChild(banner); } @@ -1092,9 +1077,9 @@ class Localuser{ }); } async showusersettings(){ - const settings = new Settings("Settings"); + const settings = new Settings(I18n.getTranslation("localuser.settings")); { - const userOptions = settings.addButton("User Settings", { ltr: true }); + const userOptions = settings.addButton(I18n.getTranslation("localuser.userSettings"), { ltr: true }); const hypotheticalProfile = document.createElement("div"); let file: undefined | File | null; let newpronouns: string | undefined; @@ -1113,7 +1098,7 @@ class Localuser{ settingsRight.addHTMLArea(hypotheticalProfile); const finput = settingsLeft.addFileInput( - "Upload pfp:", + I18n.getTranslation("uploadPfp"), _=>{ if(file){ this.updatepfp(file); @@ -1139,7 +1124,7 @@ class Localuser{ }); let bfile: undefined | File | null; const binput = settingsLeft.addFileInput( - "Upload banner:", + I18n.getTranslation("uploadBanner"), _=>{ if(bfile !== undefined){ this.updatebanner(bfile); @@ -1165,7 +1150,7 @@ class Localuser{ }); let changed = false; const pronounbox = settingsLeft.addTextInput( - "Pronouns:", + I18n.getTranslation("pronouns"), _=>{ if(newpronouns || newbio || changed){ this.updateProfile({ @@ -1182,7 +1167,7 @@ class Localuser{ newpronouns = _; regen(); }); - const bioBox = settingsLeft.addMDInput("Bio:", _=>{}, { + const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, { initText: this.user.bio.rawString, }); bioBox.watchForChange(_=>{ @@ -1197,7 +1182,7 @@ class Localuser{ color = "transparent"; } const colorPicker = settingsLeft.addColorInput( - "Profile color:", + I18n.getTranslation("profileColor"), _=>{}, { initColor: color } ); @@ -1210,11 +1195,11 @@ class Localuser{ }); } { - const tas = settings.addButton("Themes & Sounds"); + const tas = settings.addButton(I18n.getTranslation("localuser.themesAndSounds")); { const themes = ["Dark", "WHITE", "Light", "Dark-Accent"]; tas.addSelect( - "Theme:", + I18n.getTranslation("localuser.theme:"), _=>{ localStorage.setItem("theme", themes[_]); setTheme(); @@ -1231,7 +1216,7 @@ class Localuser{ const sounds = AVoice.sounds; tas .addSelect( - "Notification sound:", + I18n.getTranslation("localuser.notisound"), _=>{ AVoice.setNotificationSound(sounds[_]); }, @@ -1246,7 +1231,7 @@ class Localuser{ { const userinfos = getBulkInfo(); tas.addColorInput( - "Accent color:", + I18n.getTranslation("localuser.accentColor"), _=>{ userinfos.accent_color = _; localStorage.setItem("userinfos", JSON.stringify(userinfos)); @@ -1259,10 +1244,10 @@ class Localuser{ ); } { - const box=tas.addCheckboxInput("Enable experimental Voice support",()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))}); + const box=tas.addCheckboxInput(I18n.getTranslation("localuser.enableEVoice"),()=>{},{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)")){ + if(confirm(I18n.getTranslation("localuser.VoiceWarning"))){ localStorage.setItem("Voice enabled","true") }else{ @@ -1279,33 +1264,33 @@ class Localuser{ } } { - const update=settings.addButton("Update settings") - const sw=update.addSelect("Service Worker setting",()=>{},["False","Offline only","True"],{ + const update=settings.addButton(I18n.getTranslation("localuser.updateSettings")) + const sw=update.addSelect(I18n.getTranslation("localuser.swSettings"),()=>{},["SWOff","SWOffline","SWOn"].map(e=>I18n.getTranslation("localuser."+e)),{ 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",()=>{ + update.addButtonInput("",I18n.getTranslation("localuser.CheckUpdate"),()=>{ SW.checkUpdate(); }); - update.addButtonInput("","Clear cache",()=>{ + update.addButtonInput("",I18n.getTranslation("localuser.clearCache"),()=>{ SW.forceClear(); }); } { - const security = settings.addButton("Account Settings"); + const security = settings.addButton(I18n.getTranslation("localuser.accountSettings")); const genSecurity = ()=>{ security.removeAll(); if(this.mfa_enabled){ - security.addButtonInput("", "Disable 2FA", ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.2faDisable"), ()=>{ const form = security.addSubForm( - "2FA Disable", + I18n.getTranslation("localuser.2faDisable"), (_: any)=>{ if(_.message){ switch(_.code){ case 60008: - form.error("code", "Invalid code"); + form.error("code", I18n.getTranslation("badCode")); break; } }else{ @@ -1319,10 +1304,10 @@ class Localuser{ headers: this.headers, } ); - form.addTextInput("Code:", "code", { required: true }); + form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code", { required: true }); }); }else{ - security.addButtonInput("", "Enable 2FA", async ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.2faEnable"), async ()=>{ let secret = ""; for(let i = 0; i < 18; i++){ secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[ @@ -1330,15 +1315,15 @@ class Localuser{ ]; } const form = security.addSubForm( - "2FA Setup", + I18n.getTranslation("localuser.setUp2fa"), (_: any)=>{ if(_.message){ switch(_.code){ case 60008: - form.error("code", "Invalid code"); + form.error("code", I18n.getTranslation("localuser.badCode")); break; case 400: - form.error("password", "Incorrect password"); + form.error("password", I18n.getTranslation("localuser.badPassword")); break; } }else{ @@ -1353,22 +1338,22 @@ class Localuser{ } ); form.addTitle( - "Copy this secret into your totp(time-based one time password) app" + I18n.getTranslation("localuser.setUp2faInstruction") ); form.addText( - `Your secret is: ${secret} and it's 6 digits, with a 30 second token period` + I18n.getTranslation("localuser.2faCodeGive",secret) ); - form.addTextInput("Account Password:", "password", { + form.addTextInput(I18n.getTranslation("localuser.password:"), "password", { required: true, password: true, }); - form.addTextInput("Code:", "code", { required: true }); + form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code", { required: true }); form.setValue("secret", secret); }); } - security.addButtonInput("", "Change discriminator", ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.changeDiscriminator"), ()=>{ const form = security.addSubForm( - "Change Discriminator", + I18n.getTranslation("localuser.changeDiscriminator"), _=>{ security.returnFromSub(); }, @@ -1378,11 +1363,11 @@ class Localuser{ method: "PATCH", } ); - form.addTextInput("New discriminator:", "discriminator"); + form.addTextInput(I18n.getTranslation("lcoaluser.newDiscriminator"), "discriminator"); }); - security.addButtonInput("", "Change email", ()=>{ + security.addButtonInput("", I18n.getTranslation("lcoaluser.changeEmail"), ()=>{ const form = security.addSubForm( - "Change Email", + I18n.getTranslation("lcoaluser.changeEmail"), _=>{ security.returnFromSub(); }, @@ -1392,15 +1377,15 @@ class Localuser{ method: "PATCH", } ); - form.addTextInput("Password:", "password", { password: true }); + form.addTextInput(I18n.getTranslation("localuser.password:"), "password", { password: true }); if(this.mfa_enabled){ - form.addTextInput("Code:", "code"); + form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code"); } - form.addTextInput("New email:", "email"); + form.addTextInput(I18n.getTranslation("localuser.newEmail:"), "email"); }); - security.addButtonInput("", "Change username", ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.changeUsername"), ()=>{ const form = security.addSubForm( - "Change Username", + I18n.getTranslation("localuser.changeUsername"), _=>{ security.returnFromSub(); }, @@ -1410,11 +1395,11 @@ class Localuser{ method: "PATCH", } ); - form.addTextInput("Password:", "password", { password: true }); + form.addTextInput(I18n.getTranslation("localuser.password:"), "password", { password: true }); if(this.mfa_enabled){ - form.addTextInput("Code:", "code"); + form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code"); } - form.addTextInput("New username:", "username"); + form.addTextInput(I18n.getTranslation("localuser.newUsername"), "username"); }); security.addButtonInput("", "Change password", ()=>{ const form = security.addSubForm( diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index 7841732f..d9e3d8b7 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -188,7 +188,43 @@ "confirmDelete":"Are you sure you want to delete $1?", "serverName":"Name of server:", "yesDelete":"Yes, I'm sure", - "noDelete":"Nevermind" + "noDelete":"Nevermind", + "create":"Create guild", + "loadingDiscovery":"Loading...", + "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entrie|entries}}" + }, + "localuser":{ + "settings":"Settings", + "userSettings":"User Settings", + "themesAndSounds":"Themes & Sounds", + "theme:":"Theme", + "notisound":"Notification sound:", + "accentColor":"Accent color:", + "enableEVoice":"Enable experimental Voice support", + "VoiceWarning":"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)", + "updateSettings":"Update Settings", + "swSettings":"Service Worker setting", + "SWOff":"Off", + "SWOffline":"Offline only", + "SWOn":"On", + "clearCache":"Clear cache", + "CheckUpdate":"Check for updates", + "accountSettings":"Account Settings", + "2faDisable":"Disable 2FA", + "badCode":"Invalid code", + "2faEnable":"Enable 2FA", + "2faCode:":"Code:", + "setUp2fa":"2FA Setup", + "badPassword":"Incorrect password", + "setUp2faInstruction":"Copy this secret into your totp(time-based one time password) app", + "2faCodeGive":"Your secret is: $1 and it's 6 digits, with a 30 second token period", + "changeDiscriminator":"Change discriminator", + "newDiscriminator":"New discriminator:", + "changeEmail":"Change email", + "password:":"Password", + "newEmail:":"New email", + "changeUsername":"Change username", + "newUsername":"New username:" }, "inviteOptions":{ "title":"Invite People", @@ -206,7 +242,12 @@ "invite":{ "invitedBy":"You've been invited by $1", "alreadyJoined":"Already joined", - "accept":"Accept" + "accept":"Accept", + "noAccount":"Create an account to accept the invite", + "longInvitedBy":"$1 invited you to join $2", + "loginOrCreateAccount":"Login or create an account ⇌", + "joinUsing":"Join using invite", + "inviteLinkCode":"Invite Link/Code" }, "replyingTo":"Replying to $1", "DMs":{ @@ -215,8 +256,13 @@ "close":"Close DM" }, "user":{ - "copyId":"Copy user ID" - } + "copyId":"Copy user ID", + "online":"Online", + "offline":"Offline" + }, + "errorReconnect":"Unable to connect to the server, retrying in $1 seconds...", + "retrying":"Retrying...", + "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." }, "ru": "./ru.json" } From 00c105db28ad0bd361ddefa063e6f4b4fb97c178 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 1 Nov 2024 12:16:47 -0500 Subject: [PATCH 0019/1330] further translation support A few more things need to be done, but it's getting a lot closer --- src/webpage/i18n.ts | 2 +- src/webpage/localuser.ts | 77 ++++++++++++++-------------- src/webpage/login.ts | 15 +++--- src/webpage/markdown.ts | 30 ++++++----- src/webpage/member.ts | 13 ++--- src/webpage/register.ts | 27 +++++----- src/webpage/role.ts | 19 +++---- src/webpage/settings.ts | 10 ++-- src/webpage/translations/en.json | 88 ++++++++++++++++++++++++++++++-- src/webpage/user.ts | 19 +++---- 10 files changed, 194 insertions(+), 106 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index af21a882..6a2f533d 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -36,7 +36,7 @@ class I18n{ for(const json of this.translations){ let jsont:string|translation=json; for(const thing of path){ - if(typeof jsont !== "string" ){ + if(typeof jsont !== "string" && jsont!==undefined){ jsont=jsont[thing]; }else{ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 76a37aa6..ba030426 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -953,7 +953,7 @@ class Localuser{ content.innerHTML = ""; const title = document.createElement("h2"); - title.textContent = I18n.getTranslation("guild.disoveryTitle"); + title.textContent = I18n.getTranslation("guild.disoveryTitle",json.guilds.length+""); content.appendChild(title); const guilds = document.createElement("div"); @@ -1365,7 +1365,7 @@ class Localuser{ ); form.addTextInput(I18n.getTranslation("lcoaluser.newDiscriminator"), "discriminator"); }); - security.addButtonInput("", I18n.getTranslation("lcoaluser.changeEmail"), ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.changeEmail"), ()=>{ const form = security.addSubForm( I18n.getTranslation("lcoaluser.changeEmail"), _=>{ @@ -1401,9 +1401,9 @@ class Localuser{ } form.addTextInput(I18n.getTranslation("localuser.newUsername"), "username"); }); - security.addButtonInput("", "Change password", ()=>{ + security.addButtonInput("", I18n.getTranslation("localuser.changePassword"), ()=>{ const form = security.addSubForm( - "Change Password", + I18n.getTranslation("localuser.changePassword"), _=>{ security.returnFromSub(); }, @@ -1413,13 +1413,13 @@ class Localuser{ method: "PATCH", } ); - form.addTextInput("Old password:", "password", { password: true }); + form.addTextInput(I18n.getTranslation("localuser.oldPassword:"), "password", { password: true }); if(this.mfa_enabled){ - form.addTextInput("Code:", "code"); + form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code"); } let in1 = ""; let in2 = ""; - form.addTextInput("New password:", "").watchForChange(text=>{ + form.addTextInput(I18n.getTranslation("localuser.newPassword:"), "").watchForChange(text=>{ in1 = text; }); const copy = form.addTextInput("New password again:", ""); @@ -1430,7 +1430,7 @@ class Localuser{ if(in1 === in2){ return in1; }else{ - throw new FormError(copy, "Passwords don't match"); + throw new FormError(copy, I18n.getTranslation("localuser.PasswordsNoMatch")); } }); }); @@ -1473,8 +1473,7 @@ class Localuser{ }); }else{ container.classList.add("disabled"); - container.title = - "This connection has been disabled server-side."; + container.title = I18n.getTranslation("localuser.PasswordsNoMatch"); } connectionContainer.appendChild(container); @@ -1483,16 +1482,16 @@ class Localuser{ connections.addHTMLArea(connectionContainer); } { - const devPortal = settings.addButton("Developer Portal"); + const devPortal = settings.addButton(I18n.getTranslation("localuser.devPortal")); fetch(this.info.api + "/teams", { headers: this.headers, }).then(async (teamsRes)=>{ const teams = await teamsRes.json(); - devPortal.addButtonInput("", "Create application", ()=>{ + devPortal.addButtonInput("", I18n.getTranslation("localuser.createApp"), ()=>{ const form = devPortal.addSubForm( - "Create application", + I18n.getTranslation("localuser.createApp"), (json: any)=>{ if(json.message) form.error("name", json.message); else{ @@ -1509,7 +1508,7 @@ class Localuser{ form.addTextInput("Name:", "name", { required: true }); form.addSelect( - "Team:", + I18n.getTranslation("localuser.team:"), "team_id", ["Personal", ...teams.map((team: { name: string })=>team.name)], { @@ -1583,16 +1582,16 @@ class Localuser{ headers:this.headers, traditionalSubmit:true }); - form.addTextInput("Application name:","name",{initText:json.name}); - form.addMDInput("Description:","description",{initText:json.description}); + form.addTextInput(I18n.getTranslation("localuser.appName"),"name",{initText:json.name}); + form.addMDInput(I18n.getTranslation("localuser.description"),"description",{initText:json.description}); form.addFileInput("Icon:","icon"); - form.addTextInput("Privacy policy URL:","privacy_policy_url",{initText:json.privacy_policy_url}); - form.addTextInput("Terms of Service URL:","terms_of_service_url",{initText:json.terms_of_service_url}); - form.addCheckboxInput("Make bot publicly inviteable?","bot_public",{initState:json.bot_public}); - form.addCheckboxInput("Require code grant to invite the bot?","bot_require_code_grant",{initState:json.bot_require_code_grant}); - form.addButtonInput("",(json.bot ? "Manage" : "Add")+" bot",async ()=>{ + form.addTextInput(I18n.getTranslation("localuser.privacyPolcyURL"),"privacy_policy_url",{initText:json.privacy_policy_url}); + form.addTextInput(I18n.getTranslation("localuser.TOSURL"),"terms_of_service_url",{initText:json.terms_of_service_url}); + form.addCheckboxInput(I18n.getTranslation("localuser.publicAvaliable"),"bot_public",{initState:json.bot_public}); + form.addCheckboxInput(I18n.getTranslation("localuser.requireCode"),"bot_require_code_grant",{initState:json.bot_require_code_grant}); + form.addButtonInput("",I18n.getTranslation("localuser."+(json.bot?"manageBot":"addBot")),async ()=>{ if(!json.bot){ - if(!confirm("Are you sure you want to add a bot to this application? There's no going back.")){ + if(!confirm(I18n.getTranslation("localuser.confirmAddBot"))){ return; } const updateRes = await fetch( @@ -1614,19 +1613,19 @@ class Localuser{ }); const json = await res.json(); if(!json.bot){ - return alert("For some reason, this application doesn't have a bot (yet)."); + return alert(I18n.getTranslation("localuser.confuseNoBot")); } const bot:mainuserjson=json.bot; - const form=container.addSubForm("Editing bot "+bot.username,out=>{console.log(out)},{ + const form=container.addSubForm(I18n.getTranslation("localuser.editingBot",bot.username),out=>{console.log(out)},{ method:"PATCH", fetchURL:this.info.api + "/applications/" + appId + "/bot", headers:this.headers, traditionalSubmit:true }); - form.addTextInput("Bot username:","username",{initText:bot.username}); - form.addFileInput("Bot avatar:","avatar"); - form.addButtonInput("","Reset Token",async ()=>{ - if(!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")){ + form.addTextInput(I18n.getTranslation("localuser.botUsername"),"username",{initText:bot.username}); + form.addFileInput(I18n.getTranslation("localuser.botAvatar"),"avatar"); + form.addButtonInput("",I18n.getTranslation("localuser.resetToken"),async ()=>{ + if(!confirm(I18n.getTranslation("localuser.confirmReset"))){ return; } const updateRes = await fetch( @@ -1637,27 +1636,27 @@ class Localuser{ } ); const updateJSON = await updateRes.json(); - text.setText("Token: "+updateJSON.token); + text.setText(I18n.getTranslation("localuser.tokenDisplay",updateJSON.token)); this.botTokens.set(appId,updateJSON.token); if(this.perminfo.applications[appId]){ this.perminfo.applications[appId]=updateJSON.token; this.userinfo.updateLocal(); } }); - const text=form.addText(this.botTokens.has(appId)?"Token: "+this.botTokens.get(appId):"Token: *****************"); + const text=form.addText(I18n.getTranslation("localuser.tokenDisplay",this.botTokens.has(appId)?this.botTokens.get(appId) as string:"*****************") ); const check=form.addOptions("",{noSubmit:true}); if(!this.perminfo.applications){ this.perminfo.applications={}; this.userinfo.updateLocal(); } - const checkbox=check.addCheckboxInput("Save token to localStorage",()=>{},{initState:!!this.perminfo.applications[appId]}); + const checkbox=check.addCheckboxInput(I18n.getTranslation("localuser.saveToken"),()=>{},{initState:!!this.perminfo.applications[appId]}); checkbox.watchForChange(_=>{ if(_){ if(this.botTokens.has(appId)){ this.perminfo.applications[appId]=this.botTokens.get(appId); this.userinfo.updateLocal(); }else{ - alert("Don't know token so can't save it to localStorage, sorry"); + alert(I18n.getTranslation("localuser.noToken")); checkbox.setState(false); } }else{ @@ -1665,14 +1664,14 @@ class Localuser{ this.userinfo.updateLocal(); } }); - form.addButtonInput("","Advanced Bot Settings",()=>{ + form.addButtonInput("",I18n.getTranslation("localuser.advancedBot"),()=>{ const token=this.botTokens.get(appId); if(token){ const botc=new Bot(bot,token,this); botc.settings(); } }); - form.addButtonInput("","Bot Invite Creator",()=>{ + form.addButtonInput("",I18n.getTranslation("localuser.botInviteCreate"),()=>{ Bot.InviteMaker(appId,form,this.info); }) } @@ -1849,11 +1848,11 @@ class Localuser{ const dialog = new Dialog([ "vdiv", - ["title", "Instance stats: " + this.instancePing.name], - ["text", "Registered users: " + json.counts.user], - ["text", "Servers: " + json.counts.guild], - ["text", "Messages: " + json.counts.message], - ["text", "Members: " + json.counts.members], + ["title", I18n.getTranslation("instanceStats.name",this.instancePing.name) ], + ["text", I18n.getTranslation("instanceStats.users",json.counts.user)], + ["text", I18n.getTranslation("instanceStats.servers",json.counts.guild)], + ["text", I18n.getTranslation("instanceStats.messages",json.counts.message)], + ["text", I18n.getTranslation("instanceStats.members",json.counts.members)], ]); dialog.show(); } diff --git a/src/webpage/login.ts b/src/webpage/login.ts index a1f765ac..1804f093 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,4 +1,5 @@ import{ Dialog }from"./dialog.js"; +import { I18n } from "./i18n.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); @@ -338,7 +339,7 @@ async function getapiurls(str: string): Promise< async function checkInstance(instance?: string){ const verify = document.getElementById("verify"); try{ - verify!.textContent = "Checking Instance"; + verify!.textContent = I18n.getTranslation("login.checking"); const instanceValue = instance || (instancein as HTMLInputElement).value; const instanceinfo = (await getapiurls(instanceValue)) as { wellknown: string; @@ -351,7 +352,7 @@ async function checkInstance(instance?: string){ if(instanceinfo){ instanceinfo.value = instanceValue; localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); - verify!.textContent = "Instance is all good"; + verify!.textContent = I18n.getTranslation("login.allGood"); // @ts-ignore if(checkInstance.alt){ // @ts-ignore @@ -362,11 +363,11 @@ async function checkInstance(instance?: string){ verify!.textContent = ""; }, 3000); }else{ - verify!.textContent = "Invalid Instance, try again"; + verify!.textContent = I18n.getTranslation("login.invalid"); } }catch{ console.log("catch"); - verify!.textContent = "Invalid Instance, try again"; + verify!.textContent = I18n.getTranslation("login.invalid"); } } @@ -374,7 +375,7 @@ if(instancein){ console.log(instancein); instancein.addEventListener("keydown", ()=>{ const verify = document.getElementById("verify"); - verify!.textContent = "Waiting to check Instance"; + verify!.textContent = I18n.getTranslation("login.waiting"); if(timeout !== null && typeof timeout !== "string"){ clearTimeout(timeout); } @@ -442,7 +443,7 @@ async function login(username: string, password: string, captcha: string){ let onetimecode = ""; new Dialog([ "vdiv", - ["title", "2FA code:"], + ["title", I18n.getTranslation("2faCode")], [ "textbox", "", @@ -455,7 +456,7 @@ async function login(username: string, password: string, captcha: string){ [ "button", "", - "Submit", + I18n.getTranslation("submit"), function(){ fetch(api + "/auth/mfa/totp", { method: "POST", diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 75a4635e..ce02f07a 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -2,6 +2,7 @@ import{ Channel }from"./channel.js"; import{ Dialog }from"./dialog.js"; import{ Emoji }from"./emoji.js"; import{ Guild }from"./guild.js"; +import { I18n } from "./i18n.js"; import{ Localuser }from"./localuser.js"; import{ Member }from"./member.js"; @@ -9,8 +10,8 @@ class MarkDown{ txt: string[]; keep: boolean; stdsize: boolean; - owner: Localuser | Channel; - info: Localuser["info"]; + owner: Localuser | Channel|void; + info: Localuser["info"]|void=undefined; constructor( text: string | string[], owner: MarkDown["owner"], @@ -24,7 +25,9 @@ class MarkDown{ if(this.txt === undefined){ this.txt = []; } - this.info = owner.info; + if(owner){ + this.info = owner.info; + } this.keep = keep; this.owner = owner; this.stdsize = stdsize; @@ -32,9 +35,10 @@ class MarkDown{ get localuser(){ if(this.owner instanceof Localuser){ return this.owner; - }else{ + }else if(this.owner){ return this.owner.localuser; } + return null; } get rawString(){ return this.txt.join(""); @@ -437,7 +441,7 @@ txt[j + 1] === undefined) continue; } } - if(txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#")){ + if((txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#"))&&this.localuser){ let id = ""; let j = i + 2; const numbers = new Set(["0","1","2","3","4","5","6","7","8","9",]); @@ -485,6 +489,7 @@ txt[j + 1] === undefined) mention.textContent = `#${channel.name}`; if(!keep){ mention.onclick = _=>{ + if(!this.localuser) return; this.localuser.goToChannel(id); }; } @@ -586,7 +591,7 @@ txt[j + 1] === undefined) if( txt[i] === "<" && - (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")) + (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")&&this.owner) ){ let found = false; const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; @@ -609,6 +614,7 @@ txt[j + 1] === undefined) const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); const owner = this.owner instanceof Channel ? this.owner.guild : this.owner; + if(!owner) continue; const emoji = new Emoji( { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, owner @@ -765,20 +771,18 @@ txt[j + 1] === undefined) }else{ const full: Dialog = new Dialog([ "vdiv", - ["title", "You're leaving Spacebar"], + ["title", I18n.getTranslation("leaving")], [ "text", - "You're going to " + - Url.host + - ". Are you sure you want to go there?", + I18n.getTranslation("goingToURL",Url.host) ], [ "hdiv", - ["button", "", "Nevermind", (_: any)=>full.hide()], + ["button", "", I18n.getTranslation("nevermind"), (_: any)=>full.hide()], [ "button", "", - "Go there", + I18n.getTranslation("goThere"), (_: any)=>{ open(); full.hide(); @@ -787,7 +791,7 @@ txt[j + 1] === undefined) [ "button", "", - "Go there and trust in the future", + I18n.getTranslation("goThereTrust"), (_: any)=>{ open(); full.hide(); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 4c49ecf5..9c40a11b 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -4,6 +4,7 @@ import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, presencejson }from"./jsontypes.js"; import{ Dialog }from"./dialog.js"; +import { I18n } from "./i18n.js"; class Member extends SnowFlake{ static already = {}; @@ -213,10 +214,10 @@ class Member extends SnowFlake{ let reason = ""; const menu = new Dialog([ "vdiv", - ["title", "Kick " + this.name + " from " + this.guild.properties.name], + ["title", I18n.getTranslation("member.kick",this.name,this.guild.properties.name)], [ "textbox", - "Reason:", + I18n.getTranslation("member.reason:"), "", function(e: Event){ reason = (e.target as HTMLInputElement).value; @@ -225,7 +226,7 @@ class Member extends SnowFlake{ [ "button", "", - "submit", + I18n.getTranslation("submit"), ()=>{ this.kickAPI(reason); menu.hide(); @@ -246,10 +247,10 @@ class Member extends SnowFlake{ let reason = ""; const menu = new Dialog([ "vdiv", - ["title", "Ban " + this.name + " from " + this.guild.properties.name], + ["title", I18n.getTranslation("member.ban",this.name,this.guild.properties.name)], [ "textbox", - "Reason:", + I18n.getTranslation("member.reason",this.name,this.guild.properties.name), "", function(e: Event){ reason = (e.target as HTMLInputElement).value; @@ -258,7 +259,7 @@ class Member extends SnowFlake{ [ "button", "", - "submit", + I18n.getTranslation("submit",this.name,this.guild.properties.name), ()=>{ this.banAPI(reason); menu.hide(); diff --git a/src/webpage/register.ts b/src/webpage/register.ts index 91fa48a7..ac871e59 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -1,4 +1,6 @@ +import { I18n } from "./i18n.js"; import{ checkInstance, adduser }from"./login.js"; +import { MarkDown } from "./markdown.js"; const registerElement = document.getElementById("register"); if(registerElement){ @@ -18,8 +20,7 @@ async function registertry(e: Event){ const captchaKey = (elements[7] as HTMLInputElement)?.value; if(password !== confirmPassword){ - (document.getElementById("wrong") as HTMLElement).textContent = -"Passwords don't match"; + (document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch"); return; } @@ -83,22 +84,22 @@ function handleErrors(errors: any, elements: HTMLFormControlsCollection){ }else if(errors.password){ showError( elements[3] as HTMLElement, -"Password: " + errors.password._errors[0].message +I18n.getTranslation("register.passwordError",errors.password._errors[0].message) ); }else if(errors.username){ showError( elements[2] as HTMLElement, -"Username: " + errors.username._errors[0].message +I18n.getTranslation("register.usernameError",errors.username._errors[0].message) ); }else if(errors.email){ showError( elements[1] as HTMLElement, -"Email: " + errors.email._errors[0].message +I18n.getTranslation("register.emailError",errors.email._errors[0].message) ); }else if(errors.date_of_birth){ showError( elements[5] as HTMLElement, -"Date of Birth: " + errors.date_of_birth._errors[0].message +I18n.getTranslation("register.DOBError",errors.date_of_birth._errors[0].message) ); }else{ (document.getElementById("wrong") as HTMLElement).textContent = @@ -125,8 +126,6 @@ function showError(element: HTMLElement, message: string){ errorElement.textContent = message; } -let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null; - async function tosLogic(){ const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const apiurl = new URL(instanceInfo.api); @@ -136,14 +135,12 @@ async function tosLogic(){ const tosPage = data.instance.tosPage; if(tosPage){ -document.getElementById("TOSbox")!.innerHTML = -"I agree to the Terms of Service:"; -TOSa = document.getElementById("TOSa") as HTMLAnchorElement; -TOSa.href = tosPage; + const box=document.getElementById("TOSbox"); + if(!box) return; + box.innerHTML =""; + box.append(new MarkDown(I18n.getTranslation("register.agreeTOS",tosPage)).makeHTML()); }else{ -document.getElementById("TOSbox")!.textContent = -"This instance has no Terms of Service, accept ToS anyways:"; -TOSa = null; + document.getElementById("TOSbox")!.textContent =I18n.getTranslation("register.noTOS"); } console.log(tosPage); } diff --git a/src/webpage/role.ts b/src/webpage/role.ts index b0256dc0..d31fb647 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -142,6 +142,7 @@ class PermissionToggle implements OptionsElement{ import{ OptionsElement, Buttons }from"./settings.js"; import { Contextmenu } from "./contextmenu.js"; import { Channel } from "./channel.js"; +import { I18n } from "./i18n.js"; class RoleList extends Buttons{ permissions: [Role, Permissions][]; permission: Permissions; @@ -204,26 +205,26 @@ class RoleList extends Buttons{ this.redoButtons(); } makeguildmenus(option:Options){ - option.addButtonInput("","Display settings",()=>{ + option.addButtonInput("",I18n.getTranslation("role.displaySettings"),()=>{ const role=this.guild.roleids.get(this.curid as string); if(!role) return; - const form=option.addSubForm("Display settings",()=>{},{ + const form=option.addSubForm(I18n.getTranslation("role.displaySettings"),()=>{},{ fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid, method:"PATCH", headers:this.headers, traditionalSubmit:true }); - form.addTextInput("Role Name:","name",{ + form.addTextInput(I18n.getTranslation("role.name"),"name",{ initText:role.name }); - form.addCheckboxInput("Hoisted:","hoist",{ + form.addCheckboxInput(I18n.getTranslation("role.hoisted"),"hoist",{ initState:role.hoist }); - form.addCheckboxInput("Allow anyone to ping this role:","mentionable",{ + form.addCheckboxInput(I18n.getTranslation("role.mentionable"),"mentionable",{ initState:role.mentionable }); const color="#"+role.color.toString(16).padStart(6,"0"); - form.addColorInput("Color","color",{ + form.addColorInput(I18n.getTranslation("role.color"),"color",{ initColor:color }); form.addPreprocessor((obj:any)=>{ @@ -236,7 +237,7 @@ class RoleList extends Buttons{ static guildrolemenu=this.GuildRoleMenu(); private static ChannelRoleMenu(){ const menu=new Contextmenu("role settings"); - menu.addbutton("Remove role",function(role){ + menu.addbutton(()=>I18n.getTranslation("role.remove"),function(role){ if(!this.channel) return; console.log(role); fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{ @@ -248,8 +249,8 @@ class RoleList extends Buttons{ } private static GuildRoleMenu(){ const menu=new Contextmenu("role settings"); - menu.addbutton("Delete Role",function(role){ - if(!confirm("Are you sure you want to delete "+role.name+"?")) return; + menu.addbutton(()=>I18n.getTranslation("role.delete"),function(role){ + if(!confirm(I18n.getTranslation("role.confirmDelete"))) return; console.log(role); fetch(this.info.api+"/guilds/"+this.guild.id+"/roles/"+role.id,{ method:"DELETE", diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 8e7fd739..9c21cf23 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -1,3 +1,5 @@ +import { I18n } from "./i18n.js"; + interface OptionsElement { // generateHTML(): HTMLElement; @@ -828,9 +830,9 @@ class Options implements OptionsElement{ div.classList.add("flexltr", "savediv"); const span = document.createElement("span"); div.append(span); - span.textContent = "Careful, you have unsaved changes"; + span.textContent = I18n.getTranslation("settings.unsaved"); const button = document.createElement("button"); - button.textContent = "Save changes"; + button.textContent = I18n.getTranslation("settings.save"); div.append(button); this.haschanged = true; this.owner.changed(div); @@ -886,7 +888,7 @@ class Form implements OptionsElement{ onSubmit: (arg1: object) => void, { ltr = false, - submitText = "Submit", + submitText = I18n.getTranslation("submit"), fetchURL = "", headers = {}, method = "POST", @@ -919,7 +921,7 @@ class Form implements OptionsElement{ onSubmit: (arg1: object) => void, { ltr = false, - submitText = "Submit", + submitText = I18n.getTranslation("submit"), fetchURL = "", headers = {}, method = "POST", diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index d9e3d8b7..bb58f6ab 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -161,6 +161,19 @@ "uptimeStats":"Uptime: \n All time: $1\nThis week: $2\nToday: $3", "warnOffiline":"Instance is offline, can't connect" }, + "register":{ + "passwordError:":"Password: $1", + "usernameError":"Username: $1", + "emailError":"Email: $1", + "DOBError":"Date of Birth: $1", + "agreeTOS":"I agree to the [Terms of Service]($1):", + "noTOS":"This instance has no Terms of Service, accept ToS anyways:" + }, + "leaving":"You're leaving Spacebar", + "goingToURL":"You're going to $1. Are you sure you want to go there?", + "goThere":"Go there", + "goThereTrust":"Go there and trust in the future", + "nevermind":"Nevermind", "submit":"submit", "guild":{ "copyId":"Copy guild id", @@ -191,7 +204,21 @@ "noDelete":"Nevermind", "create":"Create guild", "loadingDiscovery":"Loading...", - "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entrie|entries}}" + "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}" + }, + "role":{ + "displaySettings":"Display settings", + "name":"Role name:", + "hoisted":"Hoisted:", + "mentionable":"Allow anyone to ping this role:", + "color":"Color", + "remove":"Remove role", + "delete":"Delete Role", + "confirmDelete":"Are you sure you want to delete $1?" + }, + "settings":{ + "unsaved":"Careful, you have unsaved changes", + "save":"Save changes" }, "localuser":{ "settings":"Settings", @@ -224,7 +251,42 @@ "password:":"Password", "newEmail:":"New email", "changeUsername":"Change username", - "newUsername":"New username:" + "newUsername":"New username:", + "changePassword":"Change password", + "oldPassword:":"Old password:", + "newPassword:":"New password:", + "PasswordsNoMatch":"Password don't match", + "disableConnection":"This connection has been disabled server-side", + "devPortal":"Developer Portal", + "createApp":"Create application", + "team:":"Team:", + "appName":"Application name:", + "description":"Description:", + "privacyPolcyURL":"Privacy policy URL:", + "TOSURL":"Terms of Service URL:", + "publicAvaliable":"Make bot publicly inviteable?", + "requireCode":"Require code grant to invite the bot?", + "manageBot":"Manage bot", + "addBot":"Add bot", + "confirmAddBot":"Are you sure you want to add a bot to this application? There's no going back.", + "confuseNoBot":"For some reason, this application doesn't have a bot (yet).", + "editingBot":"Editing bot $1", + "botUsername":"Bot username:", + "botAvatar":"Bot avatar:", + "resetToken":"Reset Token", + "confirmReset":"Are you sure you want to reset the bot token? Your bot will stop working until you update it.", + "tokenDisplay":"Token: $1", + "saveToken":"Save token to localStorage", + "noToken":"Don't know token so can't save it to localStorage, sorry", + "advancedBot":"Advanced Bot Settings", + "botInviteCreate":"Bot Invite Creator" + }, + "instanceStats":{ + "name":"Instance stats: $1", + "users":"Registered users: $1", + "servers":"Servers: $1", + "messages":"Messages: $1", + "members":"Members: $1" }, "inviteOptions":{ "title":"Invite People", @@ -239,6 +301,7 @@ "limit":"$1 {{PLURAL:$1|use|uses}}", "noLimit":"No limit" }, + "2faCode":"2FA code:", "invite":{ "invitedBy":"You've been invited by $1", "alreadyJoined":"Already joined", @@ -258,7 +321,26 @@ "user":{ "copyId":"Copy user ID", "online":"Online", - "offline":"Offline" + "offline":"Offline", + "message":"Message user", + "block":"Block user", + "unblock":"unblock user", + "friendReq":"Friend request", + "kick":"Kick member", + "ban":"Ban member", + "addRole":"Add roles", + "removeRole":"Remove roles" + }, + "login":{ + "checking":"Checking Instance", + "allGood":"All good", + "invalid":"Invalid Instance, try again", + "waiting":"Waiting to check Instance" + }, + "member":{ + "kick":"Kick $1 from $2", + "reason:":"Reason:", + "ban":"Ban $1 from $2" }, "errorReconnect":"Unable to connect to the server, retrying in $1 seconds...", "retrying":"Retrying...", diff --git a/src/webpage/user.ts b/src/webpage/user.ts index dd0f5a80..5723b86d 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -7,6 +7,7 @@ import{ SnowFlake }from"./snowflake.js"; import{ presencejson, userjson }from"./jsontypes.js"; import { Role } from "./role.js"; import { Search } from "./search.js"; +import { I18n } from "./i18n.js"; class User extends SnowFlake{ owner: Localuser; @@ -96,10 +97,10 @@ class User extends SnowFlake{ static contextmenu = new Contextmenu("User Menu"); static setUpContextMenu(): void{ - this.contextmenu.addbutton("Copy user id", function(this: User){ + this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(this: User){ navigator.clipboard.writeText(this.id); }); - this.contextmenu.addbutton("Message user", function(this: User){ + this.contextmenu.addbutton(()=>I18n.getTranslation("user.message"), function(this: User){ fetch(this.info.api + "/users/@me/channels", { method: "POST", body: JSON.stringify({ recipients: [this.id] }), @@ -111,7 +112,7 @@ class User extends SnowFlake{ }); }); this.contextmenu.addbutton( - "Block user", + ()=>I18n.getTranslation("user.block"), function(this: User){ this.block(); }, @@ -122,7 +123,7 @@ class User extends SnowFlake{ ); this.contextmenu.addbutton( - "Unblock user", + ()=>I18n.getTranslation("user.unblock"), function(this: User){ this.unblock(); }, @@ -131,7 +132,7 @@ class User extends SnowFlake{ return this.relationshipType === 2; } ); - this.contextmenu.addbutton("Friend request", function(this: User){ + this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){ fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { method: "PUT", headers: this.owner.headers, @@ -141,7 +142,7 @@ class User extends SnowFlake{ }); }); this.contextmenu.addbutton( - "Kick member", + ()=>I18n.getTranslation("user.kick"), function(this: User, member: Member | undefined){ member?.kick(); }, @@ -159,7 +160,7 @@ class User extends SnowFlake{ } ); this.contextmenu.addbutton( - "Ban member", + ()=>I18n.getTranslation("user.ban"), function(this: User, member: Member | undefined){ member?.ban(); }, @@ -177,7 +178,7 @@ class User extends SnowFlake{ } ); this.contextmenu.addbutton( - "Add roles", + ()=>I18n.getTranslation("user.addRole"), async function(this: User, member: Member | undefined,e){ if(member){ e.stopPropagation(); @@ -203,7 +204,7 @@ class User extends SnowFlake{ } ); this.contextmenu.addbutton( - "Remove roles", + ()=>I18n.getTranslation("user.removeRole"), async function(this: User, member: Member | undefined,e){ if(member){ e.stopPropagation(); From 3fb4920ab03b78eb441d9320fef2fbf57d0298f3 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 1 Nov 2024 13:17:04 -0500 Subject: [PATCH 0020/1330] finished translations --- src/webpage/home.html | 16 ++++++++-------- src/webpage/home.ts | 30 ++++++++++++++++++++++++++++++ src/webpage/index.html | 2 +- src/webpage/index.ts | 12 +++++++++++- src/webpage/login.html | 10 +++++----- src/webpage/login.ts | 15 +++++++++++++++ src/webpage/oauth2/auth.ts | 12 ++++++++---- src/webpage/register.html | 18 +++++++++--------- src/webpage/register.ts | 16 +++++++++++++++- src/webpage/translations/en.json | 24 ++++++++++++++++++++++++ 10 files changed, 126 insertions(+), 29 deletions(-) diff --git a/src/webpage/home.html b/src/webpage/home.html index ac50c0f8..8189617a 100644 --- a/src/webpage/home.html +++ b/src/webpage/home.html @@ -25,16 +25,16 @@

Jank Client

Github - + Open Client
-

Welcome to Jank Client

+

Welcome to Jank Client

-

Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:

-
    +

    Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:

    +
    • Direct Messaging
    • Reactions support
    • Invites
    • @@ -42,17 +42,17 @@

      Welcome to Jank Client

    • User settings
    • Developer portal
    • Bot invites
    • +
    • Translation support
-

Spacebar-Compatible Instances:

+

Spacebar-Compatible Instances:

-

Contribute to Jank Client

-

We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out - some typos.


+

Contribute to Jank Client

+

We always appreciate some help, whether that be in the form of bug reports, code, help translate, or even just pointing out some typos.


Github diff --git a/src/webpage/home.ts b/src/webpage/home.ts index 57c4824e..a04772b4 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -3,6 +3,36 @@ import{ mobile }from"./login.js"; console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; +(async ()=>{ + await I18n.done; + const openClient=document.getElementById("openClient") + const welcomeJank=document.getElementById("welcomeJank") + const box1title=document.getElementById("box1title") + const box1Items=document.getElementById("box1Items") + const compatableInstances=document.getElementById("compatableInstances") + const box3title=document.getElementById("box3title") + const box3description=document.getElementById("box3description") + if(openClient&&welcomeJank&&compatableInstances&&box3title&&box3description&&box1title&&box1Items){ + openClient.textContent=I18n.getTranslation("htmlPages.openClient"); + welcomeJank.textContent=I18n.getTranslation("htmlPages.welcomeJank"); + box1title.textContent=I18n.getTranslation("htmlPages.box1title"); + + compatableInstances.textContent=I18n.getTranslation("htmlPages.compatableInstances"); + box3title.textContent=I18n.getTranslation("htmlPages.box3title"); + box3description.textContent=I18n.getTranslation("htmlPages.box3description"); + + const items=I18n.getTranslation("htmlPages.box1Items").split("|"); + let i=0; + //@ts-ignore ts is being dumb here + for(const item of box1Items.children){ + (item as HTMLElement).textContent=items[i]; + i++; + } + }else{ + console.error(openClient,welcomeJank,compatableInstances,box3title,box3description,box1title,box1Items) + } +})() + fetch("/instances.json") .then(_=>_.json()) .then( diff --git a/src/webpage/index.html b/src/webpage/index.html index fcf0797c..1fef5535 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -21,7 +21,7 @@
-

Jank Client is loading

+

Jank Client is loading

This shouldn't take long

Switch Accounts

diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 1b40b9d8..54c8c9fe 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -20,7 +20,17 @@ import { I18n } from "./i18n.js"; window.location.href = "/login.html"; return; } - + { + const loadingText=document.getElementById("loadingText"); + const loaddesc=document.getElementById("load-desc"); + const switchaccounts=document.getElementById("switchaccounts"); + if(loadingText&&loaddesc&&switchaccounts){ + loadingText.textContent=I18n.getTranslation("htmlPages.loadingText"); + loaddesc.textContent=I18n.getTranslation("htmlPages.loaddesc"); + switchaccounts.textContent=I18n.getTranslation("htmlPages.switchaccounts"); + } + } + I18n function showAccountSwitcher(): void{ const table = document.createElement("div"); table.classList.add("flexttb","accountSwitcher"); diff --git a/src/webpage/login.html b/src/webpage/login.html index dcaf1d13..836e06b0 100644 --- a/src/webpage/login.html +++ b/src/webpage/login.html @@ -19,7 +19,7 @@

Login

- +

Login required > - + Login required > - + Login

- +
Don't have an account?
- \ No newline at end of file + diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 1804f093..719f19e0 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -32,6 +32,21 @@ login?: string; }[] | null; +(async ()=>{ + await I18n.done + const instanceField=document.getElementById("instanceField"); + const emailField= document.getElementById("emailField"); + const pwField= document.getElementById("pwField"); + const loginButton=document.getElementById("loginButton"); + const noAccount=document.getElementById("switch") + if(instanceField&&emailField&&pwField&&loginButton&&noAccount){ + instanceField.textContent=I18n.getTranslation("htmlPages.instanceField"); + emailField.textContent=I18n.getTranslation("htmlPages.emailField"); + pwField.textContent=I18n.getTranslation("htmlPages.pwField"); + loginButton.textContent=I18n.getTranslation("htmlPages.loginButton"); + noAccount.textContent=I18n.getTranslation("htmlPages.noAccount"); + } +})() setTheme(); function getBulkUsers(){ const json = getBulkInfo(); diff --git a/src/webpage/oauth2/auth.ts b/src/webpage/oauth2/auth.ts index b90f362e..2345f157 100644 --- a/src/webpage/oauth2/auth.ts +++ b/src/webpage/oauth2/auth.ts @@ -1,3 +1,4 @@ +import { I18n } from "../i18n.js"; import{ getBulkUsers, Specialuser, getapiurls }from"../login.js"; import { Permissions } from "../permissions.js"; type botjsonfetch={ @@ -86,7 +87,7 @@ type botjsonfetch={ if(!joinable.length){ document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot"; } - + await I18n.done; function showGuilds(user:Specialuser){ if(!urls) return; fetch(urls.api+"/oauth2/authorize/"+window.location.search,{ @@ -230,6 +231,7 @@ type botjsonfetch={ const perms=document.getElementById("permissions") as HTMLDivElement; if(perms&&permstr){ + perms.children[0].textContent=I18n.getTranslation("htmlPages.idpermissions") const permisions=new Permissions(permstr) for(const perm of Permissions.info()){ if(permisions.hasPermission(perm.name,false)){ @@ -243,7 +245,9 @@ type botjsonfetch={ } } }) - document - .getElementById("AcceptInvite")! - .addEventListener("click", showAccounts); + const AcceptInvite=document.getElementById("AcceptInvite"); + if(AcceptInvite){ + AcceptInvite.addEventListener("click", showAccounts); + AcceptInvite.textContent=I18n.getTranslation("htmlPages.addBot") + } })(); diff --git a/src/webpage/register.html b/src/webpage/register.html index 8d8b3fd5..e6727e8f 100644 --- a/src/webpage/register.html +++ b/src/webpage/register.html @@ -17,31 +17,31 @@

Create an account

- +

- +
- +
- +
- +
- +
@@ -54,11 +54,11 @@

Create an account

- +
- Already have an account? + Already have an account?
- \ No newline at end of file + diff --git a/src/webpage/register.ts b/src/webpage/register.ts index ac871e59..e9f1ce3b 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -6,7 +6,21 @@ const registerElement = document.getElementById("register"); if(registerElement){ registerElement.addEventListener("submit", registertry); } - +(async ()=>{ + await I18n.done; + const userField=document.getElementById("userField"); + const pw2Field=document.getElementById("pw2Field"); + const dobField=document.getElementById("dobField"); + const createAccount=document.getElementById("createAccount"); + const alreadyHave=document.getElementById("alreadyHave"); + if(userField&&pw2Field&&alreadyHave&&createAccount&&dobField){ + userField.textContent=I18n.getTranslation("htmlPages.userField") + pw2Field.textContent=I18n.getTranslation("htmlPages.pw2Field") + dobField.textContent=I18n.getTranslation("htmlPages.dobField") + createAccount.textContent=I18n.getTranslation("htmlPages.createAccount") + alreadyHave.textContent=I18n.getTranslation("htmlPages.alreadyHave") + } +})() async function registertry(e: Event){ e.preventDefault(); const elements = (e.target as HTMLFormElement) diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index bb58f6ab..a927fa36 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -161,6 +161,30 @@ "uptimeStats":"Uptime: \n All time: $1\nThis week: $2\nToday: $3", "warnOffiline":"Instance is offline, can't connect" }, + "htmlPages":{ + "idpermissions":"This will allow the bot to:", + "addBot":"Add to server", + "loadingText":"Jank Client is loading", + "loaddesc":"This shouldn't take long", + "switchaccounts":"Switch Accounts", + "instanceField":"Instance:", + "emailField":"Email:", + "pwField":"Password:", + "loginButton":"Login", + "noAccount":"Don't have an account?", + "userField":"Username:", + "pw2Field":"Enter password again:", + "dobField":"Date of birth:", + "createAccount":"Create account", + "alreadyHave":"Already have an account?", + "openClient":"Open Client", + "welcomeJank":"Welcome to Jank Client", + "box1title":"Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:", + "box1Items":"Direct Messaging|Reactions support|Invites|Account switching|User settings|Developer portal|Bot invites|Translation support", + "compatableInstances":"Spacebar-Compatible Instances:", + "box3title":"Contribute to Jank Client", + "box3description":"We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out some typos." + }, "register":{ "passwordError:":"Password: $1", "usernameError":"Username: $1", From 9a7c70bd55b3e3ba8c55214f6f6dbe39583fdab4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 1 Nov 2024 13:31:15 -0500 Subject: [PATCH 0021/1330] added language switcher and defualt language finder --- src/webpage/i18n.ts | 23 ++++++++++++++++- src/webpage/localuser.ts | 43 +++++++++++++++++++------------- src/webpage/translations/en.json | 5 ++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 6a2f533d..162086c9 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -105,6 +105,27 @@ class I18n{ return trans; } } + static options(){ + return ["en","ru"] + } + static setLanguage(lang:string){ + if(this.options().indexOf(userLocale)!==-1){ + localStorage.setItem("lang",lang); + I18n.create("/translations/en.json",lang); + } + } } -I18n.create("/translations/en.json","en") + +let userLocale = navigator.language.slice(0,2) || "en"; +if(I18n.options().indexOf(userLocale)===-1){ + userLocale="en"; +} +const storage=localStorage.getItem("lang"); +if(storage){ + userLocale=storage; +}else{ + localStorage.setItem("lang",userLocale) +} +I18n.create("/translations/en.json",userLocale); + export{I18n}; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index ba030426..db9dbc96 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1243,25 +1243,7 @@ class Localuser{ { initColor: userinfos.accent_color } ); } - { - const box=tas.addCheckboxInput(I18n.getTranslation("localuser.enableEVoice"),()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))}); - box.onchange=(e)=>{ - if(e){ - if(confirm(I18n.getTranslation("localuser.VoiceWarning"))){ - 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(I18n.getTranslation("localuser.updateSettings")) @@ -1434,6 +1416,31 @@ class Localuser{ } }); }); + + security.addSelect(I18n.getTranslation("localuser.language"),(e)=>{ + I18n.setLanguage(I18n.options()[e]); + },I18n.options(),{ + defaultIndex:I18n.options().indexOf(I18n.lang) + }); + { + const box=security.addCheckboxInput(I18n.getTranslation("localuser.enableEVoice"),()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))}); + box.onchange=(e)=>{ + if(e){ + if(confirm(I18n.getTranslation("localuser.VoiceWarning"))){ + localStorage.setItem("Voice enabled","true") + + }else{ + box.value=true; + const checkbox=box.input.deref(); + if(checkbox){ + checkbox.checked=false; + } + } + }else{ + localStorage.removeItem("Voice enabled"); + } + } + } }; genSecurity(); } diff --git a/src/webpage/translations/en.json b/src/webpage/translations/en.json index a927fa36..c7cce946 100644 --- a/src/webpage/translations/en.json +++ b/src/webpage/translations/en.json @@ -303,7 +303,8 @@ "saveToken":"Save token to localStorage", "noToken":"Don't know token so can't save it to localStorage, sorry", "advancedBot":"Advanced Bot Settings", - "botInviteCreate":"Bot Invite Creator" + "botInviteCreate":"Bot Invite Creator", + "language":"Language:" }, "instanceStats":{ "name":"Instance stats: $1", @@ -370,5 +371,5 @@ "retrying":"Retrying...", "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." }, - "ru": "./ru.json" + "ru": "/translations/ru.json" } From 68e7b708f2993ee762c73eac78760283b2efff88 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 1 Nov 2024 13:38:30 -0500 Subject: [PATCH 0022/1330] add md --- translations.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 translations.md diff --git a/translations.md b/translations.md new file mode 100644 index 00000000..1dec7604 --- /dev/null +++ b/translations.md @@ -0,0 +1,20 @@ +# Translations +Currently Jank Client is only in english, though I've added support for other languages in the codebase now, if you or someone else wishes to try and help us create these translations it should be rather simple. +the translations are stored in `/src/webpage/translations` if you want to help translate a pre-existing translations, you would modify the JSON files there, if you wish to add a new translation that should also be somewhat straight forward +Firstly, modify `en.json` to include your languages file like for example for russian it'd be `"ru": "/translations/ru.json"` so jank client knows where the translation is, then you'd create the file and make sure to include a `"@metadata"` thing at the top to credit yourself, then in the file you'll want to create a property of the object corisponding to the language you're trying to add, for example +```json +{ + "@metadata": { + "authors": [ + ], + "last-updated": "XXXX/XX/XX", + "locale": "ru", + "comment":"" + }, + "ru":{ + } +} +``` +Then to add the actual translations, just take the english version and in the same structure add your language, you must keep the left side, as that's what jank needs to know what the translation is. + +Thank you so much for contributing another lanuage, or even just parts, as jank will fall back to the english translation if your translation has gaps in it, so it's not all or nothing From de86347150f508cd3b5c13b37143fa3917ff843c Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 1 Nov 2024 13:40:28 -0500 Subject: [PATCH 0023/1330] more info --- translations.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/translations.md b/translations.md index 1dec7604..6e9e0ecc 100644 --- a/translations.md +++ b/translations.md @@ -17,4 +17,10 @@ Firstly, modify `en.json` to include your languages file like for example for ru ``` Then to add the actual translations, just take the english version and in the same structure add your language, you must keep the left side, as that's what jank needs to know what the translation is. -Thank you so much for contributing another lanuage, or even just parts, as jank will fall back to the english translation if your translation has gaps in it, so it's not all or nothing +Thank you so much for contributing another lanuage, or even just parts, as jank will fall back to the english translation if your translation has gaps in it, so it's not all or nothing. + +## What is the format? +It's the same format found (here)[https://github.com/wikimedia/jquery.i18n#message-file-format], though we are not using jquery, and you might notice some of the strings use markdown, but most do not. + +## I want to help correct a translation +Go ahead! We're more than happy to take corrections to translations as well! From f20e0c7e7283cc58a34b764e335ec82d866b3dde Mon Sep 17 00:00:00 2001 From: MathMan05 <73901602+MathMan05@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:42:57 -0500 Subject: [PATCH 0024/1330] it was backawrds --- translations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations.md b/translations.md index 6e9e0ecc..c7a45acd 100644 --- a/translations.md +++ b/translations.md @@ -20,7 +20,7 @@ Then to add the actual translations, just take the english version and in the sa Thank you so much for contributing another lanuage, or even just parts, as jank will fall back to the english translation if your translation has gaps in it, so it's not all or nothing. ## What is the format? -It's the same format found (here)[https://github.com/wikimedia/jquery.i18n#message-file-format], though we are not using jquery, and you might notice some of the strings use markdown, but most do not. +It's the same format found [here](https://github.com/wikimedia/jquery.i18n#message-file-format), though we are not using jquery, and you might notice some of the strings use markdown, but most do not. ## I want to help correct a translation Go ahead! We're more than happy to take corrections to translations as well! From 1004adec514578271287467f352fe1b32e61f3cc Mon Sep 17 00:00:00 2001 From: ygg2 Date: Fri, 1 Nov 2024 20:32:28 -0400 Subject: [PATCH 0025/1330] scrollbar hiding (sideDiv and firefox) --- src/webpage/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index c7fa9e9c..1070ad28 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -264,9 +264,12 @@ textarea { ::-webkit-scrollbar-thumb:hover { background: var(--primary-text-soft); } -#servers::-webkit-scrollbar, #channels::-webkit-scrollbar { +#servers::-webkit-scrollbar, #channels::-webkit-scrollbar, #sideDiv::-webkit-scrollbar { display: none; } +#servers, #channels, #sideDiv { + scrollbar-width: none; +} /* Homepage */ #titleDiv { From 15b6f851e0081a4dd9f5f45168c42c21c92e1df8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 14:05:20 -0500 Subject: [PATCH 0026/1330] fix weird nodejs thing --- src/index.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 620b25e5..fad5e827 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import path from"node:path"; import{ observe, uptime }from"./stats.js"; import{ getApiUrls, inviteResponse }from"./utils.js"; import{ fileURLToPath }from"node:url"; +import {readFileSync} from "fs"; import process from"node:process"; const devmode = (process.env.NODE_ENV || "development") === "development"; @@ -19,7 +20,34 @@ interface Instance { } const app = express(); -import instances from"./webpage/instances.json" with { type: "json" }; + +type instace={ + name:string, + description?:string, + descriptionLong?:string, + image?:string, + url?:string, + language:string, + country:string, + display:boolean, + urls?:{ + wellknown:string, + api:string, + cdn:string, + gateway:string, + login?:string + }, + contactInfo?:{ + discord?:string, + github?:string, + email?:string, + spacebar?:string, + matrix?:string, + mastodon?:string + } +} +const instances=JSON.parse(readFileSync(__dirname+"/webpage/instances.json").toString()) as instace[]; + const instanceNames = new Map(); for(const instance of instances){ From d854e7ba801c66c603196bc75b7a3348d050db27 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 14:19:19 -0500 Subject: [PATCH 0027/1330] env var for instanes.json --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index fad5e827..717a4228 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,7 @@ type instace={ mastodon?:string } } -const instances=JSON.parse(readFileSync(__dirname+"/webpage/instances.json").toString()) as instace[]; +const instances=JSON.parse(readFileSync(process.env.INSTANCES||(__dirname+"/webpage/instances.json")).toString()) as instace[]; const instanceNames = new Map(); From 198dde3d6c2fb6f0465c9be0d305af9b3e2fbedb Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 14:20:46 -0500 Subject: [PATCH 0028/1330] update name --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 717a4228..1b6040f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,7 @@ type instace={ mastodon?:string } } -const instances=JSON.parse(readFileSync(process.env.INSTANCES||(__dirname+"/webpage/instances.json")).toString()) as instace[]; +const instances=JSON.parse(readFileSync(process.env.JANK_INSTANCES_PATH||(__dirname+"/webpage/instances.json")).toString()) as instace[]; const instanceNames = new Map(); From 858437e876dd68a7f985cedd64aaa430449f27ee Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 16:13:07 -0500 Subject: [PATCH 0029/1330] put translations in a more sensable spot --- gulpfile.cjs | 9 ++++++++- {src/webpage/translations => translations}/en.json | 0 {src/webpage/translations => translations}/ru.json | 0 3 files changed, 8 insertions(+), 1 deletion(-) rename {src/webpage/translations => translations}/en.json (100%) rename {src/webpage/translations => translations}/ru.json (100%) diff --git a/gulpfile.cjs b/gulpfile.cjs index af740408..623f908d 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -70,6 +70,13 @@ gulp.task("copy-html", () => { .pipe(gulp.dest("dist")); }); +gulp.task("copy-translations", () => { + return gulp + .src("translations/*.json") + .pipe(plumber()) // Prevent pipe breaking caused by errors + .pipe(gulp.dest("dist/webpage/translations")); +}); + // Task to copy other static assets (e.g., CSS, images) gulp.task("copy-assets", () => { return gulp @@ -92,5 +99,5 @@ gulp.task("copy-assets", () => { // Default task to run all tasks gulp.task( "default", - gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets")) + gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets","copy-translations")) ); diff --git a/src/webpage/translations/en.json b/translations/en.json similarity index 100% rename from src/webpage/translations/en.json rename to translations/en.json diff --git a/src/webpage/translations/ru.json b/translations/ru.json similarity index 100% rename from src/webpage/translations/ru.json rename to translations/ru.json From ea608475295057d9dc8662901ad984f3d92b6db5 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 16:22:18 -0500 Subject: [PATCH 0030/1330] change something to use markdown instead of html --- src/webpage/localuser.ts | 4 +++- translations/en.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index db9dbc96..9947d75b 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -283,8 +283,10 @@ class Localuser{ this.errorBackoff = 0; else this.errorBackoff++; this.connectionSucceed = 0; + const loaddesc=document.getElementById("load-desc") as HTMLElement; - (document.getElementById("load-desc") as HTMLElement).innerHTML = I18n.getTranslation("errorReconnect",Math.round(0.2 + this.errorBackoff * 2.8)+"") + loaddesc.innerHTML =""; + loaddesc.append(new MarkDown(I18n.getTranslation("errorReconnect",Math.round(0.2 + this.errorBackoff * 2.8)+"")).makeHTML()); switch( this.errorBackoff //try to recover from bad domain ){ diff --git a/translations/en.json b/translations/en.json index c7cce946..961f6195 100644 --- a/translations/en.json +++ b/translations/en.json @@ -367,7 +367,7 @@ "reason:":"Reason:", "ban":"Ban $1 from $2" }, - "errorReconnect":"Unable to connect to the server, retrying in $1 seconds...", + "errorReconnect":"Unable to connect to the server, retrying in **$1** seconds...", "retrying":"Retrying...", "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." }, From 91a90ca1d4d9c88aa313d8b00f3365a121d1d765 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 16:23:52 -0500 Subject: [PATCH 0031/1330] add % that was missing thanks to booky10 --- translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/en.json b/translations/en.json index 961f6195..82770b25 100644 --- a/translations/en.json +++ b/translations/en.json @@ -158,7 +158,7 @@ "switchAccounts":"Switch accounts ⇌", "accountNotStart":"Account unable to start", "home":{ - "uptimeStats":"Uptime: \n All time: $1\nThis week: $2\nToday: $3", + "uptimeStats":"Uptime: \n All time: $1%\nThis week: $2%\nToday: $3%", "warnOffiline":"Instance is offline, can't connect" }, "htmlPages":{ From a8f55d0ae6753f1d7a52f6ce3549fd5f19f65da2 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 2 Nov 2024 16:25:19 -0500 Subject: [PATCH 0032/1330] get rid of bad innerHTML --- src/webpage/channel.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 21b0bbcd..e442ff8d 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -803,10 +803,11 @@ class Channel extends SnowFlake{ this.localuser.pageTitle("#" + this.name); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; if(this.topic){ - channelTopic.innerHTML = new MarkDown( + channelTopic.innerHTML =""; + channelTopic.append(new MarkDown( this.topic, this - ).makeHTML().innerHTML; + ).makeHTML()); channelTopic.removeAttribute("hidden"); }else channelTopic.setAttribute("hidden", ""); From e5c02823004bce45a5605935fb6879b4b2b48f44 Mon Sep 17 00:00:00 2001 From: booky10 Date: Sun, 3 Nov 2024 00:28:06 +0100 Subject: [PATCH 0033/1330] Fix gulp build sometimes erroring --- gulpfile.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index 623f908d..b8b7c776 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -99,5 +99,5 @@ gulp.task("copy-assets", () => { // Default task to run all tasks gulp.task( "default", - gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets","copy-translations")) + gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets"), "copy-translations") ); From 9434450d26ac43e17902e9e392c11f38a978c639 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sun, 3 Nov 2024 14:56:46 -0600 Subject: [PATCH 0034/1330] focus on box on reply --- src/webpage/channel.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index e442ff8d..f1451ce3 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -731,6 +731,8 @@ class Channel extends SnowFlake{ this.replyingto.div.classList.remove("replying"); } this.replyingto = message; + const typebox = document.getElementById("typebox") as HTMLElement; + typebox.focus(); if(!this.replyingto?.div)return; console.log(message); this.replyingto.div.classList.add("replying"); From 32ba377292ac7030768fe7bc4a7bff4b050838cb Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sun, 3 Nov 2024 15:53:37 -0600 Subject: [PATCH 0035/1330] fix backwards typing --- translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/en.json b/translations/en.json index 82770b25..14a661ea 100644 --- a/translations/en.json +++ b/translations/en.json @@ -133,7 +133,7 @@ "leaveGuild":"Leave Guild", "confirmGuildLeave":"Are you sure you want to leave $1", "UrlGen":"URL generator", - "typing":"$2 {{PLURAL:$1|are|is}} typing", + "typing":"$2 {{PLURAL:$1|is|are}} typing", "noMessages":"No messages appear to be here, be the first to say something!", "blankMessage":"Blank Message", "channel":{ From 740caf1df45b0ba10da893b1aa914976fa6d6b8f Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 4 Nov 2024 10:20:55 -0600 Subject: [PATCH 0036/1330] add docs again --- translations/qqq.json | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 translations/qqq.json diff --git a/translations/qqq.json b/translations/qqq.json new file mode 100644 index 00000000..bc17f5e3 --- /dev/null +++ b/translations/qqq.json @@ -0,0 +1,65 @@ +{ + "@metadata": { + "authors": [ + "MathMan05" + ], + "last-updated": "2024/11/4", + "locale": "en", + "comment":"Don't know how often I'll update this top part lol" + }, + "qqq":{ + "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then for each language it'll have a string that either contains the translation in json, or points to the translation as a string, for example you'll see \"ru\": \"/translations/ru.json\" in en.json as en.json does not contain the russian translation directly, instead it says that the russian translation is at that location. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", + "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", + "permissions":{ + "description":"This object contains the descriptions of permisions, along with their names." + }, + "channel":{ + "description":"This object contains strings related to channels, which are analogous to discord channels." + }, + "home":{ + "description":"This contains the dynamic text for the homepage" + }, + "htmlPages":{ + "description":"This contains much of the text for the html pages so that they can have translations applied as well.", + "box1Items":"this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" + }, + "register":{ + "description":"This contains the dynamic text for the register page", + "agreeTOS":"uses MarkDown" + }, + "guild":{ + "description":"This object contains strings related to guilds, which are analogous to discord guilds." + }, + "role":{ + "description":"This object contains some strings related to roles, which are analogous to discord roles." + }, + "settings":{ + "description":"This object contains some strings related to settings menu which are fairly generic" + }, + "localuser":{ + "description":"This object contains strings related to the logged in user, which is mostly the settings for the user" + }, + "instanceStats":{ + "description":"This object contains strings related to instance stats" + }, + "inviteOptions":{ + "description":"This object contains strings for the invite creator" + }, + "invite":{ + "description":"This object contains strings for invites outside of their creation" + }, + "DMs":{ + "description":"This object contains strings related to dirrect messaging" + }, + "user":{ + "description":"This object contains strings related to users" + }, + "login":{ + "description":"This object contains strings related to the login page" + }, + "member":{ + "description":"This object contains strings related to guild members" + }, + "errorReconnect":"Uses MarkDown" + } +} From 040f9317455f6a554729b974464d201f2e161bc9 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 4 Nov 2024 12:13:57 -0600 Subject: [PATCH 0037/1330] new env var --- src/stats.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stats.ts b/src/stats.ts index d1e66ac1..a0d7fe5a 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -28,7 +28,7 @@ const uptimeObject: Map = loadUptimeObject(); export{ uptimeObject as uptime }; function loadUptimeObject(): Map{ - const filePath = path.join(__dirname, "..", "uptime.json"); + const filePath = process.env.UPTIMEJSON||path.join(__dirname, "..", "uptime.json"); if(fs.existsSync(filePath)){ try{ const data = JSON.parse(fs.readFileSync(filePath, "utf8")); @@ -255,4 +255,4 @@ function setStatus(instance: string | Instance, status: boolean): void{ calcStats(instance); } } -} \ No newline at end of file +} From e2cf4d2a2b513209601440e85ca05d814d09d3e1 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 4 Nov 2024 12:15:13 -0600 Subject: [PATCH 0038/1330] forgot the other one --- src/stats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats.ts b/src/stats.ts index a0d7fe5a..7084be91 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -50,7 +50,7 @@ function saveUptimeObject(): void{ saveTimeout = setTimeout(()=>{ const data = Object.fromEntries(uptimeObject); fs.writeFile( - path.join(__dirname, "..", "uptime.json"), + process.env.UPTIMEJSON||path.join(__dirname, "..", "uptime.json"), JSON.stringify(data), error=>{ if(error){ From 6b7b342e45005a47ffac933d0830224cc707f53c Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 4 Nov 2024 12:21:36 -0600 Subject: [PATCH 0039/1330] change name --- src/stats.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stats.ts b/src/stats.ts index 7084be91..6e07e904 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -28,7 +28,7 @@ const uptimeObject: Map = loadUptimeObject(); export{ uptimeObject as uptime }; function loadUptimeObject(): Map{ - const filePath = process.env.UPTIMEJSON||path.join(__dirname, "..", "uptime.json"); + const filePath = process.env.JANK_UPTIME_JSON||path.join(__dirname, "..", "uptime.json"); if(fs.existsSync(filePath)){ try{ const data = JSON.parse(fs.readFileSync(filePath, "utf8")); @@ -50,7 +50,7 @@ function saveUptimeObject(): void{ saveTimeout = setTimeout(()=>{ const data = Object.fromEntries(uptimeObject); fs.writeFile( - process.env.UPTIMEJSON||path.join(__dirname, "..", "uptime.json"), + process.env.JANK_UPTIME_JSON||path.join(__dirname, "..", "uptime.json"), JSON.stringify(data), error=>{ if(error){ From 4128f7bd97d251b0d567766b85abcf8245fcc3fc Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 4 Nov 2024 12:22:22 -0600 Subject: [PATCH 0040/1330] one more name chance --- src/stats.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stats.ts b/src/stats.ts index 6e07e904..1356d30d 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -28,7 +28,7 @@ const uptimeObject: Map = loadUptimeObject(); export{ uptimeObject as uptime }; function loadUptimeObject(): Map{ - const filePath = process.env.JANK_UPTIME_JSON||path.join(__dirname, "..", "uptime.json"); + const filePath = process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"); if(fs.existsSync(filePath)){ try{ const data = JSON.parse(fs.readFileSync(filePath, "utf8")); @@ -50,7 +50,7 @@ function saveUptimeObject(): void{ saveTimeout = setTimeout(()=>{ const data = Object.fromEntries(uptimeObject); fs.writeFile( - process.env.JANK_UPTIME_JSON||path.join(__dirname, "..", "uptime.json"), + process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"), JSON.stringify(data), error=>{ if(error){ From 7814d610956e84ec40e5f112c97106b817eb9198 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 11:08:32 -0600 Subject: [PATCH 0041/1330] fix gulpfile and force everyone to have everyone --- gulpfile.cjs | 1 + src/webpage/member.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index b8b7c776..5c471ed0 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -89,6 +89,7 @@ gulp.task("copy-assets", () => { "src/**/*.png", "src/**/*.jpg", "src/**/*.jpeg", + "src/**/*.webp", "src/**/*.gif", "src/**/*.svg", ],{encoding:false}) diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 9c40a11b..91e08935 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -45,7 +45,12 @@ class Member extends SnowFlake{ } (this as any)[key] = (memberjson as any)[key]; } - + if(!this.user.bot){ + const everyone=this.guild.roleids.get(this.guild.id); + if(everyone&&(this.roles.indexOf(everyone)===-1)){ + this.roles.push(everyone) + } + } this.roles.sort((a, b)=>{ return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); }); @@ -164,7 +169,6 @@ class Member extends SnowFlake{ ); } hasRole(ID: string){ - console.log(this.roles, ID); for(const thing of this.roles){ if(thing.id === ID){ return true; From 0d4ca2d68adce07f62bd7a0e8020475906621049 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 11:38:40 -0600 Subject: [PATCH 0042/1330] don't display everyone role --- src/webpage/user.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 5723b86d..e47b7c35 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -508,6 +508,7 @@ class User extends SnowFlake{ const roles = document.createElement("div"); roles.classList.add("flexltr","rolesbox"); for(const role of member.roles){ + if(role.id===member.guild.id) continue; const roleDiv = document.createElement("div"); roleDiv.classList.add("rolediv"); const color = document.createElement("div"); From 49cdb8fda6791d92ae78b0e216e31232202c4e88 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 11:58:01 -0600 Subject: [PATCH 0043/1330] Various fixes --- src/webpage/channel.ts | 11 +++++++++++ src/webpage/contextmenu.ts | 28 +++++++++++++++++++++++++++- src/webpage/infiniteScroller.ts | 4 ++++ src/webpage/localuser.ts | 7 ++++--- src/webpage/login.ts | 3 ++- src/webpage/manifest.json | 7 ++++--- src/webpage/message.ts | 16 +++++++++++++++- 7 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index f1451ce3..2d71e988 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -579,6 +579,17 @@ class Channel extends SnowFlake{ } return div; } + async moveForDrag(x:number){ + const mainarea=document.getElementById("mainarea"); + if(!mainarea) return; + if(x===-1){ + mainarea.style.removeProperty("left"); + mainarea.style.removeProperty("transition"); + return; + } + mainarea.style.left=x+"px"; + mainarea.style.transition="left 0s" + } async setUpVoice(){ if(!this.voice) return; this.voice.onMemberChange=async (memb,joined)=>{ diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index a5559db1..8b37ab22 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -84,7 +84,7 @@ class Contextmenu{ Contextmenu.currentmenu = div; return this.div; } - bindContextmenu(obj: HTMLElement, addinfo: x, other: y){ + bindContextmenu(obj: HTMLElement, addinfo: x, other: y,touchDrag:(x:number,y:number)=>unknown=()=>{},touchEnd:(x:number,y:number)=>unknown=()=>{}){ const func = (event: MouseEvent)=>{ event.preventDefault(); event.stopImmediatePropagation(); @@ -92,13 +92,39 @@ class Contextmenu{ }; obj.addEventListener("contextmenu", func); if(iOS){ + let hold:NodeJS.Timeout|undefined; + let x!:number; + let y!:number; obj.addEventListener("touchstart",(event: TouchEvent)=>{ + x=event.touches[0].pageX; + y=event.touches[0].pageY; if(event.touches.length > 1){ event.preventDefault(); event.stopImmediatePropagation(); this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); + }else{ + // + event.stopImmediatePropagation(); + hold=setTimeout(()=>{ + if(lastx**2+lasty**2>10**2) return; + this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); + console.log(obj); + },500) } },{passive: false}); + let lastx=0; + let lasty=0; + obj.addEventListener("touchend",()=>{ + if(hold){ + clearTimeout(hold); + } + touchEnd(lastx,lasty); + }); + obj.addEventListener("touchmove",(event)=>{ + lastx=event.touches[0].pageX-x; + lasty=event.touches[0].pageY-y; + touchDrag(lastx,lasty); + }); } return func; } diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index ee7a4b0f..55202d8b 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -147,6 +147,7 @@ offset: number if(nextid){ const html = await this.getHTMLFromID(nextid); + if(!html){ this.destroyFromID(nextid); return false; @@ -155,6 +156,7 @@ offset: number fragment.prepend(html); this.HTMLElements.unshift([html, nextid]); this.scrollTop += this.averageheight; + } } if(this.scrollTop > this.maxDist){ @@ -162,7 +164,9 @@ offset: number if(html){ again = true; await this.destroyFromID(html[1]); + this.scrollTop -= this.averageheight; + } } if(again){ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 9947d75b..02309451 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1231,10 +1231,11 @@ class Localuser{ } { - const userinfos = getBulkInfo(); + let userinfos = getBulkInfo(); tas.addColorInput( I18n.getTranslation("localuser.accentColor"), _=>{ + userinfos = getBulkInfo(); userinfos.accent_color = _; localStorage.setItem("userinfos", JSON.stringify(userinfos)); document.documentElement.style.setProperty( @@ -1347,11 +1348,11 @@ class Localuser{ method: "PATCH", } ); - form.addTextInput(I18n.getTranslation("lcoaluser.newDiscriminator"), "discriminator"); + form.addTextInput(I18n.getTranslation("localuser.newDiscriminator"), "discriminator"); }); security.addButtonInput("", I18n.getTranslation("localuser.changeEmail"), ()=>{ const form = security.addSubForm( - I18n.getTranslation("lcoaluser.changeEmail"), + I18n.getTranslation("localuser.changeEmail"), _=>{ security.returnFromSub(); }, diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 719f19e0..7a604ac4 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -89,7 +89,7 @@ function trimswitcher(){ } function getBulkInfo(){ - return JSON.parse(localStorage.getItem("userinfos")!); + return JSON.parse(localStorage.getItem("userinfos") as string); } function setDefaults(){ let userinfos = getBulkInfo(); @@ -126,6 +126,7 @@ function setDefaults(){ }; } if(userinfos.preferences && userinfos.preferences.notisound === undefined){ + console.warn("uhoh") userinfos.preferences.notisound = "three"; } localStorage.setItem("userinfos", JSON.stringify(userinfos)); diff --git a/src/webpage/manifest.json b/src/webpage/manifest.json index dca0dc22..f78e7cd8 100644 --- a/src/webpage/manifest.json +++ b/src/webpage/manifest.json @@ -2,11 +2,12 @@ "name": "Jank Client", "icons": [ { - "src": "/logo.svg", + "src": "/logo.webp", "sizes": "512x512" } ], "start_url": "/channels/@me", "display": "standalone", - "theme_color": "#05050a" -} \ No newline at end of file + "theme_color": "#05050a", + "offline_enabled": true +} diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 574a106b..e0fb0445 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -208,7 +208,21 @@ class Message extends SnowFlake{ return this.owner.info; } messageevents(obj: HTMLDivElement){ - Message.contextmenu.bindContextmenu(obj, this, undefined); + Message.contextmenu.bindContextmenu(obj, this, undefined,(x)=>{ + //console.log(x,y); + this.channel.moveForDrag(Math.max(x,0)); + + },(x,y)=>{ + console.log(x,y); + this.channel.moveForDrag(-1); + if(x>60){ + console.log("In here?") + const toggle = document.getElementById("maintoggle") as HTMLInputElement; + toggle.checked = false; + console.log(toggle); + } + + },); this.div = obj; obj.classList.add("messagediv"); } From 673359ef65a8bdec1025507dc71cda2e794a5608 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 16:12:12 -0600 Subject: [PATCH 0044/1330] add some tolorance --- src/webpage/message.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index e0fb0445..c961d36b 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -208,11 +208,17 @@ class Message extends SnowFlake{ return this.owner.info; } messageevents(obj: HTMLDivElement){ + let drag=false; Message.contextmenu.bindContextmenu(obj, this, undefined,(x)=>{ //console.log(x,y); + if(!drag&&x<20){ + return + } + drag=true; this.channel.moveForDrag(Math.max(x,0)); },(x,y)=>{ + drag=false; console.log(x,y); this.channel.moveForDrag(-1); if(x>60){ From f3a24546b0efb5834c70d58bc314afe293f82ab0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 16:25:09 -0600 Subject: [PATCH 0045/1330] fix contextmenu --- src/webpage/contextmenu.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 8b37ab22..5cfa0172 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -53,11 +53,11 @@ class Contextmenu{ let visibleButtons = 0; for(const thing of this.buttons){ - if(!thing[3].bind(addinfo).call(addinfo, other))continue; + if(!thing[3].call(addinfo, other))continue; visibleButtons++; const intext = document.createElement("button"); - intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); + intext.disabled = !thing[4].call(addinfo, other); intext.classList.add("contextbutton"); if(thing[0] instanceof Function){ intext.textContent = thing[0](); @@ -66,7 +66,10 @@ class Contextmenu{ } console.log(thing); if(thing[5] === "button" || thing[5] === "submenu"){ - intext.onclick = thing[1].bind(addinfo, other); + intext.onclick = (e)=>{ + div.remove(); + thing[1].call(addinfo, other,e) + }; } div.appendChild(intext); From 5fe6355358cebc30809e87fa0e712e957eab0207 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 16:25:23 -0600 Subject: [PATCH 0046/1330] fix DMs on mobile --- src/webpage/direct.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 85685749..3e41b5e8 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -163,6 +163,8 @@ class Group extends Channel{ (div as any).myinfo = this; div.onclick = _=>{ this.getHTML(); + const toggle = document.getElementById("maintoggle") as HTMLInputElement; + toggle.checked = true; }; return div; @@ -290,6 +292,8 @@ class Group extends Channel{ div.onclick = _=>{ this.guild.loadGuild(); this.getHTML(); + const toggle = document.getElementById("maintoggle") as HTMLInputElement; + toggle.checked = true; }; }else if(current){ current.remove(); From 19e2b5c26997ce828962bbe151d0bba6dafac7f4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 6 Nov 2024 16:43:29 -0600 Subject: [PATCH 0047/1330] implement resume --- src/webpage/jsontypes.ts | 6 ++- src/webpage/localuser.ts | 112 +++++++++++++++++++++++---------------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 9e38b133..39114cf2 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -499,7 +499,11 @@ roleCreate | { op: 0, t: "GUILD_MEMBER_UPDATE", d: memberjson, - "s": 3 + s: 3 +}|{ + op:9, + d:boolean, + s:number }|memberlistupdatejson|voiceupdate|voiceserverupdate; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 02309451..47fc9a0f 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -14,7 +14,7 @@ import { Role } from "./role.js"; import { VoiceFactory } from "./voice.js"; import { I18n } from "./i18n.js"; -const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); +const wsCodesRetry = new Set([4000,4001,4002, 4003, 4005, 4007, 4008, 4009]); class Localuser{ badges: Map = new Map(); @@ -74,6 +74,8 @@ class Localuser{ this.guildids = new Map(); this.user = new User(ready.d.user, this); this.user.setstatus("online"); + this.resume_gateway_url=ready.d.resume_gateway_url; + this.session_id=ready.d.session_id; this.voiceFactory=new VoiceFactory({id:this.user.id}); this.handleVoice(); @@ -141,16 +143,21 @@ class Localuser{ this.guilds = []; this.guildids = new Map(); if(this.ws){ - this.ws.close(4001); + this.ws.close(4040); } } swapped = false; - async initwebsocket(): Promise{ + resume_gateway_url?:string; + session_id?:string; + async initwebsocket(resume=false): Promise{ let returny: () => void; + if(!this.resume_gateway_url||!this.session_id){ + resume=false; + } const ws = new WebSocket( - this.serverurls.gateway.toString() + - "?encoding=json&v=9" + - (DecompressionStream ? "&compress=zlib-stream" : "") + (resume?this.resume_gateway_url:this.serverurls.gateway.toString()) + +"?encoding=json&v=9" + + (DecompressionStream ? "&compress=zlib-stream" : "") ); this.ws = ws; let ds: DecompressionStream; @@ -168,28 +175,43 @@ class Localuser{ returny = res; ws.addEventListener("open", _event=>{ console.log("WebSocket connected"); - ws.send( - JSON.stringify({ - op: 2, - d: { - token: this.token, - capabilities: 16381, - properties: { - browser: "Jank Client", - client_build_number: 0, //might update this eventually lol - release_channel: "Custom", - browser_user_agent: navigator.userAgent, - }, - compress: Boolean(DecompressionStream), - presence: { - status: "online", - since: null, //new Date().getTime() - activities: [], - afk: false, + if(resume){ + ws.send( + JSON.stringify({ + op: 6, + d: { + token: this.token, + session_id: this.session_id, + seq: this.lastSequence + } + }) + ); + this.resume_gateway_url=undefined; + this.session_id=undefined; + }else{ + ws.send( + JSON.stringify({ + op: 2, + d: { + token: this.token, + capabilities: 16381, + properties: { + browser: "Jank Client", + client_build_number: 0, //might update this eventually lol + release_channel: "Custom", + browser_user_agent: navigator.userAgent, + }, + compress: Boolean(DecompressionStream), + presence: { + status: "online", + since: null, //new Date().getTime() + activities: [], + afk: false, + }, }, - }, - }) - ); + }) + ); + } }); const textdecode = new TextDecoder(); if(DecompressionStream){ @@ -261,35 +283,29 @@ class Localuser{ ws.addEventListener("close", async event=>{ this.ws = undefined; console.log("WebSocket closed with code " + event.code); - + if((event.code > 1000 && event.code < 1016) || (wsCodesRetry.has(event.code)&&this.errorBackoff===0)){ + this.errorBackoff++; + this.initwebsocket(true).then(()=>{ + this.loaduser(); + }); + return; + } this.unload(); - (document.getElementById("loading") as HTMLElement).classList.remove( - "doneloading" - ); - (document.getElementById("loading") as HTMLElement).classList.add( - "loading" - ); + (document.getElementById("loading") as HTMLElement).classList.remove("doneloading"); + (document.getElementById("loading") as HTMLElement).classList.add("loading"); this.fetchingmembers = new Map(); this.noncemap = new Map(); this.noncebuild = new Map(); - if( - (event.code > 1000 && event.code < 1016) || - wsCodesRetry.has(event.code) - ){ - if( - this.connectionSucceed !== 0 && - Date.now() > this.connectionSucceed + 20000 - ) + if((event.code > 1000 && event.code < 1016) || wsCodesRetry.has(event.code)||event.code==4041){ + if(this.connectionSucceed !== 0 && Date.now() > this.connectionSucceed + 20000){ this.errorBackoff = 0; - else this.errorBackoff++; + }else this.errorBackoff++; this.connectionSucceed = 0; const loaddesc=document.getElementById("load-desc") as HTMLElement; loaddesc.innerHTML =""; loaddesc.append(new MarkDown(I18n.getTranslation("errorReconnect",Math.round(0.2 + this.errorBackoff * 2.8)+"")).makeHTML()); - switch( - this.errorBackoff //try to recover from bad domain - ){ + switch(this.errorBackoff){//try to recover from bad domain case 3: const newurls = await getapiurls(this.info.wellknown); if(newurls){ @@ -348,6 +364,10 @@ class Localuser{ async handleEvent(temp: wsjson){ console.debug(temp); if(temp.s)this.lastSequence = temp.s; + if(temp.op ===9&&this.ws){ + this.errorBackoff=0; + this.ws.close(4041); + } if(temp.op == 0){ switch(temp.t){ case"MESSAGE_CREATE": From ccc795f9828e9a08c44dd935bc3adcb2fa8ebcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20Alp=20G=C3=BCney?= Date: Fri, 8 Nov 2024 08:19:47 +0300 Subject: [PATCH 0048/1330] Added Turkish translation --- translations/tr.json | 375 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 translations/tr.json diff --git a/translations/tr.json b/translations/tr.json new file mode 100644 index 00000000..2fb52984 --- /dev/null +++ b/translations/tr.json @@ -0,0 +1,375 @@ +{ + "@metadata": { + "authors": [ + "Erkin Alp Güney" + ], + "last-updated": "2024/11/24", + "locale": "tr", + "comment": "Sizli bizli" + }, + "tr": { + "reply": "Yanıtla", + "copyrawtext": "Ham metni kopyala", + "copymessageid": "Mesaj kimliğini kopyala", + "permissions": { + "descriptions": { + "CREATE_INSTANT_INVITE": "Kullanıcının sunucu için davet oluşturmasına izin verir", + "KICK_MEMBERS": "Kullanıcının sunucudan üyeleri atmasına izin verir", + "BAN_MEMBERS": "Kullanıcının sunucudan üyeleri yasaklamasına izin verir", + "ADMINISTRATOR": "Tüm izinlere izin verir ve kanal izin geçersiz kılmalarını atlar. Bu tehlikeli bir izindir!", + "MANAGE_CHANNELS": "Kullanıcının kanalları yönetmesine ve düzenlemesine izin verir", + "MANAGE_GUILD": "Sunucunun yönetimine ve düzenlenmesine izin verir", + "ADD_REACTIONS": "Kullanıcının mesajlara tepki eklemesine izin verir", + "VIEW_AUDIT_LOG": "Kullanıcının denetim günlüğünü görüntülemesine izin verir", + "PRIORITY_SPEAKER": "Ses kanalında öncelikli konuşmacı kullanımına izin verir", + "STREAM": "Kullanıcının yayın yapmasına izin verir", + "VIEW_CHANNEL": "Kullanıcının kanalı görüntülemesine izin verir", + "SEND_MESSAGES": "Kullanıcının mesaj göndermesine izin verir", + "SEND_TTS_MESSAGES": "Kullanıcının metinden sese mesajlar göndermesine izin verir", + "MANAGE_MESSAGES": "Kullanıcının kendisine ait olmayan mesajları silmesine izin verir", + "EMBED_LINKS": "Bu kullanıcı tarafından gönderilen bağlantıların otomatik olarak gömülmesine izin verir", + "ATTACH_FILES": "Kullanıcının dosya eklemesine izin verir", + "READ_MESSAGE_HISTORY": "Kullanıcının mesaj geçmişini okumasına izin verir", + "MENTION_EVERYONE": "Kullanıcının herkesi etiketlemesine izin verir", + "USE_EXTERNAL_EMOJIS": "Kullanıcının harici emojileri kullanmasına izin verir", + "VIEW_GUILD_INSIGHTS": "Kullanıcının sunucu içgörülerini görmesine izin verir", + "CONNECT": "Kullanıcının bir ses kanalına bağlanmasına izin verir", + "SPEAK": "Kullanıcının bir ses kanalında konuşmasına izin verir", + "MUTE_MEMBERS": "Kullanıcının diğer üyeleri susturmasına izin verir", + "DEAFEN_MEMBERS": "Kullanıcının diğer üyeleri sağırlamasına izin verir", + "MOVE_MEMBERS": "Kullanıcının üyeleri ses kanalları arasında taşımasına izin verir", + "USE_VAD": "Kullanıcıların ses etkinliği ile konuşmasına izin verir", + "CHANGE_NICKNAME": "Kullanıcının kendi takma adını değiştirmesine izin verir", + "MANAGE_NICKNAMES": "Kullanıcının diğer üyelerin takma adlarını değiştirmesine izin verir", + "MANAGE_ROLES": "Kullanıcının rolleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_WEBHOOKS": "Webhook'ların yönetimine ve düzenlenmesine izin verir", + "MANAGE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panolarını yönetmeye izin verir", + "USE_APPLICATION_COMMANDS": "Kullanıcının uygulama komutlarını kullanmasına izin verir", + "REQUEST_TO_SPEAK": "Kullanıcının sahne kanalında konuşma isteğinde bulunmasına izin verir", + "MANAGE_EVENTS": "Kullanıcının etkinlikleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_THREADS": "Kullanıcının konuları silmesine ve arşivlemesine ve tüm özel konuları görüntülemesine izin verir", + "CREATE_PUBLIC_THREADS": "Kullanıcının genel konular oluşturmasına izin verir", + "CREATE_PRIVATE_THREADS": "Kullanıcının özel konular oluşturmasına izin verir", + "USE_EXTERNAL_STICKERS": "Kullanıcının harici çıkartmaları kullanmasına izin verir", + "SEND_MESSAGES_IN_THREADS": "Kullanıcının konularda mesaj göndermesine izin verir", + "USE_EMBEDDED_ACTIVITIES": "Kullanıcının gömülü etkinlikleri kullanmasına izin verir", + "MODERATE_MEMBERS": "Kullanıcının diğer kullanıcıları susturmasına izin verir", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Abonelik içgörülerini görüntülemeye izin verir", + "USE_SOUNDBOARD": "Ses panosunu bir ses kanalında kullanmaya izin verir", + "CREATE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panosu sesleri oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "CREATE_EVENTS": "Zamanlanmış etkinlikler oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "USE_EXTERNAL_SOUNDS": "Diğer sunuculardan özel ses panosu seslerini kullanmaya izin verir", + "SEND_VOICE_MESSAGES": "Sesli mesajlar göndermeye izin verir", + "SEND_POLLS": "Anketler göndermeye izin verir", + "USE_EXTERNAL_APPS": "Kullanıcı tarafından yüklenen uygulamaların herkese açık yanıtlar göndermesine izin verir. Devre dışı bırakıldığında, kullanıcılar yine de uygulamalarını kullanabilir ancak yanıtlar geçici olacaktır. Bu, sunucuya yüklenmemiş uygulamalar için geçerlidir." + }, + "readableNames": { + "CREATE_INSTANT_INVITE": "Davet oluştur", + "KICK_MEMBERS": "Üyeleri at", + "BAN_MEMBERS": "Üyeleri yasakla", + "ADMINISTRATOR": "Yönetici", + "MANAGE_CHANNELS": "Kanalları yönet", + "MANAGE_GUILD": "Sunucuyu yönet", + "ADD_REACTIONS": "Tepkiler ekle", + "VIEW_AUDIT_LOG": "Denetim günlüğünü görüntüle", + "PRIORITY_SPEAKER": "Öncelikli konuşmacı", + "STREAM": "Video", + "VIEW_CHANNEL": "Kanalları görüntüle", + "SEND_MESSAGES": "Mesaj gönder", + "SEND_TTS_MESSAGES": "Metinden sese mesaj gönder", + "MANAGE_MESSAGES": "Mesajları yönet", + "EMBED_LINKS": "Bağlantıları göm", + "ATTACH_FILES": "Dosyaları ekle", + "READ_MESSAGE_HISTORY": "Mesaj geçmişini oku", + "MENTION_EVERYONE": "@everyone, @here ve tüm rolleri etiketle", + "USE_EXTERNAL_EMOJIS": "Harici emojileri kullan", + "VIEW_GUILD_INSIGHTS": "Sunucu içgörülerini görüntüle", + "CONNECT": "Bağlan", + "SPEAK": "Konuş", + "MUTE_MEMBERS": "Üyeleri sustur", + "DEAFEN_MEMBERS": "Üyeleri sağırla", + "MOVE_MEMBERS": "Üyeleri taşı", + "USE_VAD": "Ses etkinliği algılamayı kullan", + "CHANGE_NICKNAME": "Takma adı değiştir", + "MANAGE_NICKNAMES": "Takma adları yönet", + "MANAGE_ROLES": "Rolleri yönet", + "MANAGE_WEBHOOKS": "Webhook'ları yönet", + "MANAGE_GUILD_EXPRESSIONS": "İfadeleri yönet", + "USE_APPLICATION_COMMANDS": "Uygulama komutlarını kullan", + "REQUEST_TO_SPEAK": "Konuşma isteğinde bulun", + "MANAGE_EVENTS": "Etkinlikleri yönet", + "MANAGE_THREADS": "Konuları yönet", + "CREATE_PUBLIC_THREADS": "Genel konular oluştur", + "CREATE_PRIVATE_THREADS": "Özel konular oluştur", + "USE_EXTERNAL_STICKERS": "Harici çıkartmaları kullan", + "SEND_MESSAGES_IN_THREADS": "Konularda mesaj gönder", + "USE_EMBEDDED_ACTIVITIES": "Etkinlikleri kullan", + "MODERATE_MEMBERS": "Üyeleri zaman aşımına uğrat", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "İçerik üretici gelir analitiğini görüntüle", + "USE_SOUNDBOARD": "Ses panosunu kullan", + "CREATE_GUILD_EXPRESSIONS": "İfadeler oluştur", + "CREATE_EVENTS": "Etkinlikler oluştur", + "USE_EXTERNAL_SOUNDS": "Harici sesleri kullan", + "SEND_VOICE_MESSAGES": "Sesli mesajlar gönder", + "SEND_POLLS": "Anketler oluştur", + "USE_EXTERNAL_APPS": "Harici uygulamaları kullan" + } + }, + "hideBlockedMessages": "Bu kullanıcıyı engellediniz, bu mesajları gizlemek için tıklayın.", + "showBlockedMessages": "Bu kullanıcıyı engellediniz, $1 engellenmiş mesajı görmek için tıklayın.", + "deleteConfirm": "Bunu silmek istediğinizden emin misiniz?", + "yes": "Evet", + "no": "Hayır", + "todayAt": "Bugün saat $1", + "yesterdayAt": "Dün saat $1", + "otherAt": "$1 tarihinde saat $2", + "botSettings": "Bot Ayarları", + "uploadPfp": "Profil resmi yükle:", + "uploadBanner": "Afiş yükle:", + "pronouns": "Zamirler:", + "bio": "Biyografi:", + "profileColor": "Profil rengi", + "botGuilds": "Botun bulunduğu sunucular:", + "leaveGuild": "Sunucudan ayrıl", + "confirmGuildLeave": "$1 sunucusundan ayrılmak istediğinizden emin misiniz", + "UrlGen": "URL oluşturucu", + "typing": "$2 {{PLURAL:$1|yazıyor|yazıyorlar}}", + "noMessages": "Burada mesaj görünmüyor, ilk siz bir şey söyleyin!", + "blankMessage": "Boş Mesaj", + "channel": { + "copyId": "Kanal kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "settings": "Ayarlar", + "delete": "Kanalı sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "voice": "Ses", + "text": "Metin", + "announcement": "Duyurular", + "name:": "Ad:", + "topic:": "Konu:", + "nsfw:": "NSFW:", + "selectType": "Kanal türünü seç", + "selectName": "Kanal adı", + "selectCatName": "Kategori adı", + "createChannel": "Kanal oluştur", + "createCatagory": "Kategori oluştur" + }, + "switchAccounts": "Hesapları Değiştir ⇌", + "accountNotStart": "Hesap başlatılamadı", + "home": { + "uptimeStats": "Çalışma Süresi: \n Tüm zamanlar: $1%\nBu hafta: $2%\nBugün: $3%", + "warnOffiline": "Örnek çevrimdışı, bağlanılamıyor" + }, + "htmlPages": { + "idpermissions": "Bu bot şunlara izin verecek:", + "addBot": "Sunucuya ekle", + "loadingText": "Jank Client yükleniyor", + "loaddesc": "Bu uzun sürmemeli", + "switchaccounts": "Hesapları Değiştir", + "instanceField": "Örnek:", + "emailField": "E-posta:", + "pwField": "Parola:", + "loginButton": "Giriş Yap", + "noAccount": "Hesabınız yok mu?", + "userField": "Kullanıcı adı:", + "pw2Field": "Parolayı tekrar girin:", + "dobField": "Doğum tarihi:", + "createAccount": "Hesap oluştur", + "alreadyHave": "Zaten bir hesabınız var mı?", + "openClient": "İstemciyi Aç", + "welcomeJank": "Jank Client'a Hoş Geldiniz", + "box1title": "Jank Client, birçok özellikle mümkün olduğunca iyi olmaya çalışan bir Spacebar uyumlu istemcidir, bunlar arasında:", + "box1Items": "Doğrudan Mesajlaşma|Tepkiler desteği|Davetler|Hesap değiştirme|Kullanıcı ayarları|Geliştirici portalı|Bot davetleri|Çeviri desteği", + "compatableInstances": "Spacebar Uyumlu Örnekler:", + "box3title": "Jank Client'a Katkıda Bulunun", + "box3description": "Her zaman yardımı takdir ederiz, ister hata raporları, ister kod şeklinde olsun, hatta sadece bazı yazım hatalarını işaret etseniz bile." + }, + "register": { + "passwordError:": "Parola: $1", + "usernameError": "Kullanıcı adı: $1", + "emailError": "E-posta: $1", + "DOBError": "Doğum Tarihi: $1", + "agreeTOS": "[Hizmet Şartlarını]($1) kabul ediyorum:", + "noTOS": "Bu örneğin Hizmet Şartları yok, yine de TOS'u kabul et:" + }, + "leaving": "Spacebar'dan ayrılıyorsunuz", + "goingToURL": "$1 adresine gidiyorsunuz. Oraya gitmek istediğinizden emin misiniz?", + "goThere": "Oraya git", + "goThereTrust": "Oraya git ve gelecekte güven", + "nevermind": "Boşver", + "submit": "Gönder", + "guild": { + "copyId": "Sunucu kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "notifications": "Bildirimler", + "leave": "Sunucudan ayrıl", + "settings": "Ayarlar", + "delete": "Sunucuyu sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "name:": "Ad:", + "topic:": "Konu:", + "icon:": "Simge:", + "overview": "Genel Bakış", + "banner:": "Afiş:", + "region:": "Bölge:", + "roles": "Roller", + "selectnoti": "Bildirim türünü seç", + "all": "hepsi", + "onlyMentions": "sadece bahsetmeler", + "none": "hiçbiri", + "confirmLeave": "Ayrılmak istediğinizden emin misiniz?", + "yesLeave": "Evet, eminim", + "noLeave": "Boşver", + "confirmDelete": "$1 sunucusunu silmek istediğinizden emin misiniz?", + "serverName": "Sunucu adı:", + "yesDelete": "Evet, eminim", + "noDelete": "Boşver", + "create": "Sunucu oluştur", + "loadingDiscovery": "Yükleniyor...", + "disoveryTitle": "Sunucu keşfi ($1) girdi" + }, + "role": { + "displaySettings": "Görüntü Ayarları", + "name": "Rol adı:", + "hoisted": "Listede göster:", + "mentionable": "Herkes bu rolü etiketleyebilir:", + "color": "Renk", + "remove": "Rolü kaldır", + "delete": "Rolü Sil", + "confirmDelete": "$1 silmek istediğinizden emin misiniz?" + }, + "settings": { + "unsaved": "Dikkatli olun, kaydedilmemiş değişiklikleriniz var", + "save": "Değişiklikleri kaydet" + }, + "localuser": { + "settings": "Ayarlar", + "userSettings": "Kullanıcı Ayarları", + "themesAndSounds": "Temalar & Sesler", + "theme:": "Tema", + "notisound": "Bildirim sesi:", + "accentColor": "Vurgu rengi:", + "enableEVoice": "Deneysel Ses desteğini etkinleştir", + "VoiceWarning": "Bunu etkinleştirmek istediğinizden emin misiniz, bu çok deneysel ve sorunlara neden olabilir. (bu özellik geliştiriciler içindir, ne yaptığınızı bilmiyorsanız lütfen etkinleştirmeyin)", + "updateSettings": "Ayarları Güncelle", + "swSettings": "Servis Çalışanı ayarı", + "SWOff": "Kapalı", + "SWOffline": "Yalnızca Çevrimdışı", + "SWOn": "Açık", + "clearCache": "Önbelleği temizle", + "CheckUpdate": "Güncellemeleri kontrol et", + "accountSettings": "Hesap Ayarları", + "2faDisable": "2FA'yı Devre Dışı Bırak", + "badCode": "Geçersiz kod", + "2faEnable": "2FA'yı Etkinleştir", + "2faCode:": "Kod:", + "setUp2fa": "2FA Kurulumu", + "badPassword": "Yanlış parola", + "setUp2faInstruction": "Bu gizli anahtarı TOTP uygulamanıza kopyalayın", + "2faCodeGive": "Gizli anahtarınız: $1 ve 6 haneli, 30 saniyelik bir token süresi var", + "changeDiscriminator": "Ayırıcıyı değiştir", + "newDiscriminator": "Yeni ayırıcı:", + "changeEmail": "E-postayı değiştir", + "password:": "Parola", + "newEmail:": "Yeni e-posta", + "changeUsername": "Kullanıcı adını değiştir", + "newUsername": "Yeni kullanıcı adı:", + "changePassword": "Parolayı değiştir", + "oldPassword:": "Eski parola:", + "newPassword:": "Yeni parola:", + "PasswordsNoMatch": "Parolalar eşleşmiyor", + "disableConnection": "Bu bağlantı sunucu tarafından devre dışı bırakıldı", + "devPortal": "Geliştirici Portalı", + "createApp": "Uygulama oluştur", + "team:": "Takım:", + "appName": "Uygulama adı:", + "description": "Açıklama:", + "privacyPolcyURL": "Gizlilik politikası URL'si:", + "TOSURL": "Hizmet Şartları URL'si:", + "publicAvaliable": "Botun herkese açık olarak davet edilmesine izin verilsin mi?", + "requireCode": "Botu davet etmek için kod izni gerekiyor mu?", + "manageBot": "Botu yönet", + "addBot": "Bot ekle", + "confirmAddBot": "Bu uygulamaya bir bot eklemek istediğinizden emin misiniz? Geri dönüşü yok.", + "confuseNoBot": "Nedense, bu uygulamanın henüz bir botu yok.", + "editingBot": "Bot $1 düzenleniyor", + "botUsername": "Bot kullanıcı adı:", + "botAvatar": "Bot avatarı:", + "resetToken": "Token'i sıfırla", + "confirmReset": "Bot token'ini sıfırlamak istediğinizden emin misiniz? Botunuz, güncelleyene kadar çalışmayı durduracak.", + "tokenDisplay": "Token: $1", + "saveToken": "Token'i localStorage'a kaydet", + "noToken": "Token'i bilmiyorum, bu yüzden localStorage'a kaydedemem, üzgünüm", + "advancedBot": "Gelişmiş Bot Ayarları", + "botInviteCreate": "Bot Davet Oluşturucu", + "language": "Dil:" + }, + "instanceStats": { + "name": "Örnek istatistikleri: $1", + "users": "Kayıtlı kullanıcılar: $1", + "servers": "Sunucular: $1", + "messages": "Mesajlar: $1", + "members": "Üyeler: $1" + }, + "inviteOptions": { + "title": "Kişileri Davet Et", + "30m": "30 Dakika", + "1h": "1 Saat", + "6h": "6 Saat", + "12h": "12 Saat", + "1d": "1 Gün", + "7d": "7 Gün", + "30d": "30 Gün", + "never": "Asla", + "limit": "$1 kullanım", + "noLimit": "Sınırsız" + }, + "2faCode": "2FA kodu:", + "invite": { + "invitedBy": "$1 sizi davet etti", + "alreadyJoined": "Zaten katıldınız", + "accept": "Kabul et", + "noAccount": "Daveti kabul etmek için bir hesap oluşturun", + "longInvitedBy": "$1 sizi $2'ya katılmaya davet etti", + "loginOrCreateAccount": "Giriş yapın veya hesap oluşturun ⇌", + "joinUsing": "Daveti kullanarak katıl", + "inviteLinkCode": "Davet Bağlantısı/Kodu" + }, + "replyingTo": "$1 yanıtlıyor", + "DMs": { + "copyId": "DM kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "close": "DM'yi kapat" + }, + "user": { + "copyId": "Kullanıcı kimliğini kopyala", + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "message": "Kullanıcıya mesaj gönder", + "block": "Kullanıcıyı engelle", + "unblock": "Engeli kaldır", + "friendReq": "Arkadaşlık isteği", + "kick": "Üyeyi at", + "ban": "Üyeyi yasakla", + "addRole": "Roller ekle", + "removeRole": "Roller kaldır" + }, + "login": { + "checking": "Örnek kontrol ediliyor", + "allGood": "Her şey yolunda", + "invalid": "Geçersiz örnek, tekrar deneyin", + "waiting": "Örneği kontrol etmek için bekleniyor" + }, + "member": { + "kick": "$1'ı $2'dan at", + "reason:": "Sebep:", + "ban": "$1'ı $2'dan yasakla" + }, + "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", + "retrying": "Yeniden deneniyor...", + "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." + }, + "ru": "/translations/ru.json" +} From 1c5a22a8722d524d70e95fd9c0af1a873fca32ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20Alp=20G=C3=BCney?= Date: Fri, 8 Nov 2024 08:21:13 +0300 Subject: [PATCH 0049/1330] Fix recursive json reference --- translations/tr.json | 1 - 1 file changed, 1 deletion(-) diff --git a/translations/tr.json b/translations/tr.json index 2fb52984..fc594949 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -371,5 +371,4 @@ "retrying": "Yeniden deneniyor...", "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." }, - "ru": "/translations/ru.json" } From 20fabefad6b6f559433d922d40c59cc461d767b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkin=20Alp=20G=C3=BCney?= Date: Fri, 8 Nov 2024 08:21:39 +0300 Subject: [PATCH 0050/1330] Add the turkish reference --- translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/translations/en.json b/translations/en.json index 14a661ea..a2164919 100644 --- a/translations/en.json +++ b/translations/en.json @@ -372,4 +372,5 @@ "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." }, "ru": "/translations/ru.json" + "tr": "/translations/tr.json" } From 472da30b362008fd9419f6abb9bb0fd9dbf3ba9e Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 8 Nov 2024 15:05:37 -0600 Subject: [PATCH 0051/1330] changes and fixes --- src/webpage/localuser.ts | 6 ++++++ src/webpage/member.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 47fc9a0f..20b9fd3c 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -534,6 +534,10 @@ class Localuser{ guild.memberupdate(temp.d) break } + default :{ + //@ts-ignore + console.warn("Unhandled case "+temp.t,temp); + } } @@ -549,6 +553,8 @@ class Localuser{ if(this.connectionSucceed === 0)this.connectionSucceed = Date.now(); this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); }, this.heartbeat_interval); + }else{ + console.log("Unhandled case "+temp.d,temp); } } get currentVoice(){ diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 91e08935..56b87c7a 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -254,7 +254,7 @@ class Member extends SnowFlake{ ["title", I18n.getTranslation("member.ban",this.name,this.guild.properties.name)], [ "textbox", - I18n.getTranslation("member.reason",this.name,this.guild.properties.name), + I18n.getTranslation("member.reason:",this.name,this.guild.properties.name), "", function(e: Event){ reason = (e.target as HTMLInputElement).value; From 26ac410da96e0adddf3586961f6752b7da219563 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 8 Nov 2024 15:17:38 -0600 Subject: [PATCH 0052/1330] add turkish --- src/webpage/contextmenu.ts | 2 +- src/webpage/guild.ts | 10 +++++----- src/webpage/i18n.ts | 2 +- src/webpage/localuser.ts | 2 +- src/webpage/message.ts | 4 ++-- translations/en.json | 9 +++++++-- translations/tr.json | 2 +- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 5cfa0172..8d6eac38 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -38,7 +38,7 @@ class Contextmenu{ return{}; } addsubmenu( - text: string, + text: string|(()=>string), onclick: (this: x, arg: y, e: MouseEvent) => void, img = null, shown: (this: x, arg: y) => boolean = _=>true, diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index dd6091d4..73cf47a9 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -302,7 +302,7 @@ class Guild extends SnowFlake{ [ "button", "", - I18n.getTranslation("yesLeave"), + I18n.getTranslation("guild.yesLeave"), (_: any)=>{ this.leave().then(_=>{ full.hide(); @@ -312,7 +312,7 @@ class Guild extends SnowFlake{ [ "button", "", - I18n.getTranslation("noLeave"), + I18n.getTranslation("guild.noLeave"), (_: any)=>{ full.hide(); }, @@ -473,7 +473,7 @@ class Guild extends SnowFlake{ ], [ "textbox", - I18n.getTranslation("serverName"), + I18n.getTranslation("guild.serverName"), "", function(this: HTMLInputElement){ confirmname = this.value; @@ -484,7 +484,7 @@ class Guild extends SnowFlake{ [ "button", "", - I18n.getTranslation("yesDelete"), + I18n.getTranslation("guild.yesDelete"), (_: any)=>{ console.log(confirmname); if(confirmname !== this.properties.name){ @@ -498,7 +498,7 @@ class Guild extends SnowFlake{ [ "button", "", - I18n.getTranslation("noDelete"), + I18n.getTranslation("guild.noDelete"), (_: any)=>{ full.hide(); }, diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 162086c9..e960b167 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -106,7 +106,7 @@ class I18n{ } } static options(){ - return ["en","ru"] + return ["en","ru","tr"] } static setLanguage(lang:string){ if(this.options().indexOf(userLocale)!==-1){ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 20b9fd3c..acbf011a 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1474,7 +1474,7 @@ class Localuser{ genSecurity(); } { - const connections = settings.addButton("Connections"); + const connections = settings.addButton(I18n.getTranslation("localuser.connections")); const connectionContainer = document.createElement("div"); connectionContainer.id = "connection-container"; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index c961d36b..555c7052 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -66,7 +66,7 @@ class Message extends SnowFlake{ navigator.clipboard.writeText(this.id); }); Message.contextmenu.addsubmenu( - "Add reaction", + ()=>I18n.getTranslation("message.reactionAdd"), function(this: Message, _, e: MouseEvent){ Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{ this.reactionToggle(_); @@ -74,7 +74,7 @@ class Message extends SnowFlake{ } ); Message.contextmenu.addbutton( - "Edit", + ()=>I18n.getTranslation("message.delete"), function(this: Message){ this.setEdit(); }, diff --git a/translations/en.json b/translations/en.json index a2164919..fa35eb10 100644 --- a/translations/en.json +++ b/translations/en.json @@ -304,7 +304,12 @@ "noToken":"Don't know token so can't save it to localStorage, sorry", "advancedBot":"Advanced Bot Settings", "botInviteCreate":"Bot Invite Creator", - "language":"Language:" + "language":"Language:", + "connections":"Connections" + }, + "message":{ + "reactionAdd":"Add reaction", + "delete":"Delete message" }, "instanceStats":{ "name":"Instance stats: $1", @@ -371,6 +376,6 @@ "retrying":"Retrying...", "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." }, - "ru": "/translations/ru.json" + "ru": "/translations/ru.json", "tr": "/translations/tr.json" } diff --git a/translations/tr.json b/translations/tr.json index fc594949..6422ad9b 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -370,5 +370,5 @@ "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", "retrying": "Yeniden deneniyor...", "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." - }, + } } From 4a64972cd11c4e74dc49b96c9b4a62b82e179b26 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 11 Nov 2024 22:49:42 -0600 Subject: [PATCH 0053/1330] mention/channel autofil --- src/webpage/channel.ts | 10 ++ src/webpage/index.html | 3 + src/webpage/index.ts | 6 +- src/webpage/localuser.ts | 260 +++++++++++++++++++++++++++++++++++++-- src/webpage/markdown.ts | 154 +++++++++++++++++++---- src/webpage/member.ts | 13 ++ src/webpage/message.ts | 4 +- src/webpage/style.css | 31 +++++ translations/en.json | 3 +- 9 files changed, 438 insertions(+), 46 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 2d71e988..4ba0d356 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -856,6 +856,16 @@ class Channel extends SnowFlake{ setTimeout(this.rendertyping.bind(this), 10000); this.rendertyping(); } + similar(str:string){ + if(this.type===4) return -1; + const strl=Math.max(str.length,1) + if(this.name.includes(str)){ + return strl/this.name.length; + }else if(this.name.toLowerCase().includes(str.toLowerCase())){ + return strl/this.name.length/1.2; + } + return 0; + } rendertyping(): void{ const typingtext = document.getElementById("typing") as HTMLDivElement; let build = ""; diff --git a/src/webpage/index.html b/src/webpage/index.html index 1fef5535..d2aa1c28 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -74,6 +74,9 @@

Server Name

+
+
+
diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 54c8c9fe..27ec5717 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -157,6 +157,7 @@ import { I18n } from "./i18n.js"; let replyingTo: Message | null = null; async function handleEnter(event: KeyboardEvent): Promise{ + if(thisUser.keyup(event)){return} const channel = thisUser.channelfocus; if(!channel)return; @@ -198,15 +199,14 @@ import { I18n } from "./i18n.js"; } } - interface CustomHTMLDivElement extends HTMLDivElement { - markdown: MarkDown; - } + interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} const typebox = document.getElementById("typebox") as CustomHTMLDivElement; const markdown = new MarkDown("", thisUser); typebox.markdown = markdown; typebox.addEventListener("keyup", handleEnter); typebox.addEventListener("keydown", event=>{ + thisUser.keydown(event) if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); }); markdown.giveBox(typebox); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index acbf011a..869268ab 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -5,10 +5,10 @@ import{ AVoice }from"./audio.js"; import{ User }from"./user.js"; import{ Dialog }from"./dialog.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{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,userjson,wsjson,}from"./jsontypes.js"; import{ Member }from"./member.js"; import{ Form, FormError, Options, Settings }from"./settings.js"; -import{ MarkDown }from"./markdown.js"; +import{ getTextNodeAtPosition, MarkDown, saveCaretPosition }from"./markdown.js"; import { Bot } from "./bot.js"; import { Role } from "./role.js"; import { VoiceFactory } from "./voice.js"; @@ -67,6 +67,7 @@ class Localuser{ }; } async gottenReady(ready: readyjson): Promise{ + await I18n.done; this.initialized = true; this.ready = ready; @@ -77,6 +78,8 @@ class Localuser{ this.resume_gateway_url=ready.d.resume_gateway_url; this.session_id=ready.d.session_id; + this.mdBox(); + this.voiceFactory=new VoiceFactory({id:this.user.id}); this.handleVoice(); this.mfa_enabled = ready.d.user.mfa_enabled as boolean; @@ -1711,11 +1714,226 @@ class Localuser{ Bot.InviteMaker(appId,form,this.info); }) } + typeMd?:MarkDown; + readonly autofillregex=Object.freeze(/[@#:]([a-z0-9 ]*)$/i); + mdBox(){ + interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} + + const typebox = document.getElementById("typebox") as CustomHTMLDivElement; + this.typeMd=typebox.markdown; + this.typeMd.onUpdate=this.search.bind(this); + } + MDReplace(replacewith:string,original:string){ + const typebox = document.getElementById("typebox") as HTMLDivElement; + if(!this.typeMd)return; + let raw=this.typeMd.rawString; + raw=raw.split(original)[1]; + if(raw===undefined) return; + raw=original.replace(this.autofillregex,"")+replacewith+raw; + console.log(raw); + console.log(replacewith); + console.log(original); + this.typeMd.txt = raw.split(""); + this.typeMd.boxupdate(typebox); + } + MDSearchOptions(options:[string,string][],original:string){ + console.warn(original); + const div=document.getElementById("searchOptions"); + if(!div)return; + div.innerHTML=""; + let i=0; + const htmloptions:HTMLSpanElement[]=[]; + for(const thing of options){ + if(i==8){ + break; + } + i++; + const span=document.createElement("span"); + htmloptions.push(span); + span.textContent=thing[0]; + span.onclick=(e)=>{ + + if(e){ + const selection = window.getSelection() as Selection; + const typebox = document.getElementById("typebox") as HTMLDivElement; + if(selection){ + console.warn(original); + const pos = getTextNodeAtPosition(typebox, original.length-(original.match(this.autofillregex) as RegExpMatchArray)[0].length+thing[1].length); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node, pos.position); + selection.addRange(range); + } + e.preventDefault(); + typebox.focus(); + } + this.MDReplace(thing[1],original); + div.innerHTML=""; + remove(); + } + div.prepend(span); + } + const remove=()=>{ + if(div&&div.innerHTML===""){ + this.keyup=()=>false; + this.keydown=()=>{}; + return true; + } + return false; + } + if(htmloptions[0]){ + let curindex=0; + let cur=htmloptions[0]; + cur.classList.add("selected"); + const cancel=new Set(["ArrowUp","ArrowDown","Enter","Tab"]); + this.keyup=(event)=>{ + if(remove()) return false; + if(cancel.has(event.key)){ + switch(event.key){ + case "ArrowUp": + if(htmloptions[curindex+1]){ + cur.classList.remove("selected"); + curindex++; + cur=htmloptions[curindex]; + cur.classList.add("selected"); + } + break; + case "ArrowDown": + if(htmloptions[curindex-1]){ + cur.classList.remove("selected"); + curindex--; + cur=htmloptions[curindex]; + cur.classList.add("selected"); + } + break; + case "Enter": + case "Tab": + //@ts-ignore + cur.onclick(); + break; + } + return true; + } + return false; + } + this.keydown=(event)=>{ + if(remove()) return; + if(cancel.has(event.key)){ + event.preventDefault(); + } + } + }else{ + remove(); + } + } + MDFindChannel(name:string,orginal:string){ + const maybe:[number,Channel][]=[]; + if(this.lookingguild&&this.lookingguild.id!=="@me"){ + for(const channel of this.lookingguild.channels){ + const confidence=channel.similar(name); + if(confidence>0){ + maybe.push([confidence,channel]); + } + } + } + maybe.sort((a,b)=>b[0]-a[0]); + this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `]),orginal); + } + async getUser(id:string){ + if(this.userMap.has(id)){ + return this.userMap.get(id) as User; + } + return new User(await (await fetch(this.info.api+"/users/"+id)).json(),this); + } + MDFineMentionGen(name:string,original:string){ + let members:[Member,number][]=[]; + if(this.lookingguild){ + for(const member of this.lookingguild.members){ + const rank=member.compare(name); + if(rank>0){ + members.push([member,rank]) + } + } + } + members.sort((a,b)=>a[1]-b[1]); + console.log(members); + this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `]),original); + } + MDFindMention(name:string,original:string){ + console.log(original); + if(this.ws&&this.lookingguild){ + this.MDFineMentionGen(name,original); + const nonce=Math.floor(Math.random()*10**8)+""; + if(this.lookingguild.member_count<=this.lookingguild.members.size) return; + this.ws.send(JSON.stringify( + {op:8, + d:{ + guild_id:[this.lookingguild.id], + query:name, + limit:8, + presences:true, + nonce + } + } + )); + this.searchMap.set(nonce,async (e)=>{ + console.log(e); + if(e.members&&e.members[0]){ + if(e.members[0].user){ + for(const thing of e.members){ + await Member.new(thing,this.lookingguild as Guild) + } + }else{ + const prom1:Promise[]=[]; + for(const thing of e.members){ + prom1.push(this.getUser(thing.id)); + } + Promise.all(prom1); + for(const thing of e.members){ + if(!this.userMap.has(thing.id)){ + console.warn("Dumb server bug for this member",thing); + continue; + } + await Member.new(thing,this.lookingguild as Guild) + } + } + this.MDFineMentionGen(name,original); + } + }) + } + } + search(str:string,pre:boolean){ + if(!pre){ + const match=str.match(this.autofillregex); + + if(match){ + console.log(str,match); + const [type, search]=[match[0][0],match[0].split(/@|#|:/)[1]]; + console.log(type,search); + switch(type){ + case "#": + this.MDFindChannel(search,str); + break; + case "@": + this.MDFindMention(search,str); + break; + case ":": + if(search.length>=2){ + console.log("implement me"); + } + break; + } + return + } + } + const div=document.getElementById("searchOptions"); + if(!div)return; + div.innerHTML=""; + } + keydown:(event:KeyboardEvent)=>unknown=()=>{}; + keyup:(event:KeyboardEvent)=>boolean=()=>false; //---------- resolving members code ----------- - readonly waitingmembers: Map< - string, - Map void> - > = new Map(); + readonly waitingmembers = new Map void>>(); readonly presences: Map = new Map(); async resolvemember( id: string, @@ -1757,19 +1975,35 @@ class Localuser{ fetchingmembers: Map = new Map(); noncemap: Map void> = new Map(); noncebuild: Map = new Map(); + searchMap=new Mapunknown>(); async gotChunk(chunk: { - chunk_index: number; - chunk_count: number; - nonce: string; - not_found?: string[]; - members?: memberjson[]; - presences: presencejson[]; - }){ + chunk_index: number; + chunk_count: number; + nonce: string; + not_found?: string[]; + members?: memberjson[]; + presences: presencejson[]; + }){ for(const thing of chunk.presences){ if(thing.user){ this.presences.set(thing.user.id, thing); } } + if(this.searchMap.has(chunk.nonce)){ + const func=this.searchMap.get(chunk.nonce); + this.searchMap.delete(chunk.nonce); + if(func){ + func(chunk); + return; + } + } chunk.members ??= []; const arr = this.noncebuild.get(chunk.nonce); if(!arr)return; diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index ce02f07a..1179d1ee 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -687,7 +687,9 @@ txt[j + 1] === undefined) e.target.classList.remove("spoiler"); e.target.classList.add("unspoiled"); } - giveBox(box: HTMLDivElement){ + onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}; + giveBox(box: HTMLDivElement,onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}){ + this.onUpdate=onUpdate; box.onkeydown = _=>{ //console.log(_); }; @@ -698,7 +700,9 @@ txt[j + 1] === undefined) prevcontent = content; this.txt = content.split(""); this.boxupdate(box); + MarkDown.gatherBoxText(box); } + }; box.onpaste = _=>{ if(!_.clipboardData)return; @@ -717,7 +721,10 @@ txt[j + 1] === undefined) box.append(this.makeHTML({ keep: true })); if(restore){ restore(); + const test=saveCaretPosition(box); + if(test) test(); } + this.onUpdate(text,formatted); } static gatherBoxText(element: HTMLElement): string{ if(element.tagName.toLowerCase() === "img"){ @@ -729,11 +736,17 @@ txt[j + 1] === undefined) if(element.hasAttribute("real")){ return element.getAttribute("real") as string; } + if(element.tagName.toLowerCase() === "pre"||element.tagName.toLowerCase() === "samp"){ + formatted=true; + }else{ + formatted=false; + } let build = ""; for(const thing of Array.from(element.childNodes)){ if(thing instanceof Text){ const text = thing.textContent; build += text; + continue; } const text = this.gatherBoxText(thing as HTMLElement); @@ -747,10 +760,7 @@ txt[j + 1] === undefined) static safeLink(elm: HTMLElement, url: string){ if(URL.canParse(url)){ const Url = new URL(url); - if( - elm instanceof HTMLAnchorElement && - this.trustedDomains.has(Url.host) - ){ + if(elm instanceof HTMLAnchorElement && this.trustedDomains.has(Url.host)){ elm.href = url; elm.target = "_blank"; return; @@ -820,20 +830,81 @@ txt[j + 1] === undefined) //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div let text = ""; -function saveCaretPosition(context: Node){ - const selection = window.getSelection(); +let formatted=false; +function saveCaretPosition(context: HTMLElement){ + const selection = window.getSelection() as Selection; if(!selection)return; const range = selection.getRangeAt(0); - range.setStart(context, 0); - text = selection.toString(); - let len = text.length + 1; - for(const str in text.split("\n")){ - if(str.length !== 0){ - len--; + let base=selection.anchorNode as Node; + range.setStart(base, 0); + let baseString:string; + if(!(base instanceof Text)){ + let i=0; + const index=selection.focusOffset; + //@ts-ignore + for(const thing of base.childNodes){ + if(i===index){ + base=thing; + break; + } + i++; + } + if(base instanceof HTMLElement){ + baseString=MarkDown.gatherBoxText(base) + }else{ + baseString=base.textContent as string; } + }else{ + baseString=selection.toString(); } - len += Number(text.at(-1) === "\n"); + + range.setStart(context, 0); + + let build=""; + //I think this is working now :3 + function crawlForText(context:Node){ + //@ts-ignore + const children=[...context.childNodes]; + if(children.length===1&&children[0] instanceof Text){ + if(selection.containsNode(context,false)){ + build+=MarkDown.gatherBoxText(context as HTMLElement); + }else if(selection.containsNode(context,true)){ + if(context.contains(base)||context===base||base.contains(context)){ + build+=baseString; + }else{ + build+=context.textContent; + } + }else{ + console.error(context); + } + return; + } + for(const node of children as Node[]){ + + if(selection.containsNode(node,false)){ + if(node instanceof HTMLElement){ + build+=MarkDown.gatherBoxText(node); + }else{ + build+=node.textContent; + } + }else if(selection.containsNode(node,true)){ + if(node instanceof HTMLElement){ + crawlForText(node); + }else{ + console.error(node,"This shouldn't happen") + } + }else{ + console.error(node,"This shouldn't happen"); + } + } + } + crawlForText(context); + if(baseString==="\n"){ + build+=baseString; + } + text=build; + const len=build.length; return function restore(){ if(!selection)return; const pos = getTextNodeAtPosition(context, len); @@ -844,20 +915,49 @@ function saveCaretPosition(context: Node){ }; } -function getTextNodeAtPosition(root: Node, index: number){ - const NODE_TYPE = NodeFilter.SHOW_TEXT; - const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem=>{ - if(!elem.textContent)return 0; - if(index > elem.textContent.length){ - index -= elem.textContent.length; - return NodeFilter.FILTER_REJECT; +function getTextNodeAtPosition(root: Node, index: number):{ + node: Node, + position: number, + }{ + if(root instanceof Text){ + return{ + node: root, + position: index, + }; + }else if(root instanceof HTMLBRElement){ + return{ + node: root, + position: 0, + }; + }else if(root instanceof HTMLElement&&root.hasAttribute("real")){ + return{ + node: root, + position: -1, + }; + } + for(const node of root.childNodes as unknown as Node[]){ + let len:number + if(node instanceof HTMLElement){ + len=MarkDown.gatherBoxText(node).length; + }else{ + len=(node.textContent as string).length } - return NodeFilter.FILTER_ACCEPT; - }); - const c = treeWalker.nextNode(); + if(lenI18n.getTranslation("message.delete"), + ()=>I18n.getTranslation("message.edit"), function(this: Message){ this.setEdit(); }, @@ -84,7 +84,7 @@ class Message extends SnowFlake{ } ); Message.contextmenu.addbutton( - "Delete message", + ()=>I18n.getTranslation("message.delete"), function(this: Message){ this.delete(); }, diff --git a/src/webpage/style.css b/src/webpage/style.css index 1070ad28..28d0feec 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -21,6 +21,37 @@ body { display: flex; flex-direction: column; } +#searchOptions{ + padding:.05in .15in; + border-radius: .1in; + background: var(--channels-bg); + position:absolute; + bottom:0; + width: calc(100% - 32px); + box-sizing: border-box; + span { + transition: background .1s; + margin-bottom:.025in; + margin-top:.025in; + padding:.075in .05in; + border-radius:.03in; + cursor:pointer; + } + span.selected{ + background:var(--button-bg); + } + span:hover{ + background:var(--button-bg); + } + +; + margin: 16px; + border: solid .025in var(--black); +} +#searchOptions:empty{ + padding: 0; + border: 0; +} .flexgrow { flex-grow: 1; min-height: 0; diff --git a/translations/en.json b/translations/en.json index fa35eb10..b11b49c1 100644 --- a/translations/en.json +++ b/translations/en.json @@ -309,7 +309,8 @@ }, "message":{ "reactionAdd":"Add reaction", - "delete":"Delete message" + "delete":"Delete message", + "edit":"Edit message" }, "instanceStats":{ "name":"Instance stats: $1", From 3805b4a63b2c99fa6433448a6f8f1e2272fdaabe Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 11 Nov 2024 22:59:29 -0600 Subject: [PATCH 0054/1330] reduce uneeded logging --- src/webpage/localuser.ts | 5 ----- src/webpage/member.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 869268ab..882c60b5 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1737,7 +1737,6 @@ class Localuser{ this.typeMd.boxupdate(typebox); } MDSearchOptions(options:[string,string][],original:string){ - console.warn(original); const div=document.getElementById("searchOptions"); if(!div)return; div.innerHTML=""; @@ -1856,11 +1855,9 @@ class Localuser{ } } members.sort((a,b)=>a[1]-b[1]); - console.log(members); this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `]),original); } MDFindMention(name:string,original:string){ - console.log(original); if(this.ws&&this.lookingguild){ this.MDFineMentionGen(name,original); const nonce=Math.floor(Math.random()*10**8)+""; @@ -1907,9 +1904,7 @@ class Localuser{ const match=str.match(this.autofillregex); if(match){ - console.log(str,match); const [type, search]=[match[0][0],match[0].split(/@|#|:/)[1]]; - console.log(type,search); switch(type){ case "#": this.MDFindChannel(search,str); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index c6493195..77bc7160 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -135,7 +135,7 @@ class Member extends SnowFlake{ } return 0; } - return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username)) + return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username),similar(this.id)); } static async resolveMember( user: User, From ebce301cbf5afd908ecd52d4571b0527cbdd9e2d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 00:40:15 -0600 Subject: [PATCH 0055/1330] more fixes --- src/webpage/localuser.ts | 2 +- src/webpage/member.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 882c60b5..eea41679 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1854,7 +1854,7 @@ class Localuser{ } } } - members.sort((a,b)=>a[1]-b[1]); + members.sort((a,b)=>b[1]-a[1]); this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `]),original); } MDFindMention(name:string,original:string){ diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 77bc7160..70ea4b22 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -135,7 +135,7 @@ class Member extends SnowFlake{ } return 0; } - return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username),similar(this.id)); + return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username),similar(this.id)/1.5); } static async resolveMember( user: User, From 71aa1c0e9bc4fd1783726521b7390f9f227089d7 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 13:46:44 -0600 Subject: [PATCH 0056/1330] fixing guild/channel creation --- package.json | 8 ++++---- src/webpage/guild.ts | 2 ++ src/webpage/localuser.ts | 20 +++++++++++--------- src/webpage/member.ts | 1 + 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 41b75666..b8fd3347 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jankclient", - "version": "0.1.0", + "version": "0.2.0", "description": "A SpaceBar Client written in TS HTML and CSS to run, clone the repo and do either `npm start` or `bun start` both bun and node are supported, and both should function as expected. To access Jank Client after init simply go to http://localhost:8080/login and login with your username and password.", "main": ".dist/index.js", "type": "module", @@ -15,8 +15,8 @@ "@html-eslint/parser": "^0.27.0", "@stylistic/eslint-plugin-js": "^2.8.0", "@swc/core": "^1.7.26", - "@typescript-eslint/eslint-plugin": "^8.6.0", - "@typescript-eslint/parser": "^8.6.0", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", "compression": "^1.7.4", "eslint-plugin-html": "^8.1.1", "express": "^4.19.2", @@ -41,6 +41,6 @@ "gulp-plumber": "^1.2.1", "gulp-typescript": "^6.0.0-alpha.1", "typescript": "^5.6.2", - "typescript-eslint": "^8.6.0" + "typescript-eslint": "^8.14.0" } } diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 73cf47a9..73a76956 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -208,6 +208,7 @@ class Guild extends SnowFlake{ } this.sortRoles(); if(member instanceof User){ + console.warn(member); Member.resolveMember(member, this).then(_=>{ if(_){ this.member = _; @@ -415,6 +416,7 @@ class Guild extends SnowFlake{ divy.append(noti); if(guild instanceof Guild){ guild.localuser.guildhtml.set(guild.id, divy); + guild.html=divy; } let icon: string | null; if(guild instanceof Guild){ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index eea41679..7dede8ec 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -436,7 +436,7 @@ class Localuser{ } break; } - case"GUILD_CREATE": { + case"GUILD_CREATE": (async()=>{ const guildy = new Guild(temp.d, this, this.user); this.guilds.push(guildy); this.guildids.set(guildy.id, guildy); @@ -444,8 +444,9 @@ class Localuser{ guildy.generateGuildIcon(), document.getElementById("bottomseparator") ); - break; - } + + })(); + break; case"MESSAGE_REACTION_ADD": { temp.d.guild_id ??= "@me"; @@ -600,7 +601,7 @@ class Localuser{ if(!guild)return; const channel = guild.createChannelpac(json); if(json.guild_id === this.lookingguild?.id){ - this.loadGuild(json.guild_id); + this.loadGuild(json.guild_id,true); } if(channel.id === this.gotoid){ guild.loadGuild(); @@ -743,7 +744,7 @@ class Localuser{ } if(json.guild_id === this.lookingguild?.id){ - this.loadGuild(json.guild_id); + this.loadGuild(json.guild_id,true); } } init(): void{ @@ -770,12 +771,13 @@ class Localuser{ return false; } } - loadGuild(id: string): Guild | undefined{ + loadGuild(id: string,forceReload=false): Guild | undefined{ let guild = this.guildids.get(id); if(!guild){ guild = this.guildids.get("@me"); } - if(this.lookingguild === guild){ + console.log(forceReload); + if((!forceReload)&&(this.lookingguild === guild)){ return guild; } if(this.channelfocus){ @@ -1939,11 +1941,11 @@ class Localuser{ } const guild = this.guildids.get(guildid); const borked = true; - if(borked && guild && guild.member_count > 250){ + if( !guild || borked && guild.member_count > 250){ //sorry puyo, I need to fix member resolving while it's broken on large guilds try{ const req = await fetch( - this.info.api + "/guilds/" + guild.id + "/members/" + id, + this.info.api + "/guilds/" + guildid + "/members/" + id, { headers: this.headers, } diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 70ea4b22..3de6ed88 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -158,6 +158,7 @@ class Member extends SnowFlake{ } }); user.members.set(guild, promise); + return await promise; } if(maybe instanceof Promise){ return await maybe; From 8af8e4dd03cd09121a4443cc83ecdee89072b63a Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 14:40:07 -0600 Subject: [PATCH 0057/1330] fix various issues and change translation stuff --- gulpfile.cjs | 12 +- src/webpage/guild.ts | 4 +- src/webpage/i18n.ts | 42 +-- src/webpage/login.ts | 115 ++++--- src/webpage/register.ts | 2 +- translations/en.json | 740 ++++++++++++++++++++-------------------- translations/tr.json | 727 +++++++++++++++++++-------------------- 7 files changed, 821 insertions(+), 821 deletions(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index 5c471ed0..a1560a69 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -6,6 +6,7 @@ const argv = require("yargs").argv; const rimraf = require("rimraf"); const plumber = require("gulp-plumber"); const sourcemaps = require('gulp-sourcemaps'); +const fs=require("fs"); const swcOptions = { jsc: { parser: { @@ -69,14 +70,21 @@ gulp.task("copy-html", () => { .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist")); }); - gulp.task("copy-translations", () => { + let langs=fs.readdirSync("translations"); + langs=langs.filter((e)=>e!=="qqq.json"); + const langobj={}; + for(const lang of langs){ + const json=JSON.parse(fs.readFileSync("translations/"+lang).toString()); + langobj[lang]=json.readableName; + } + if(!fs.existsSync("dist/webpage/translations")) fs.mkdirSync("dist/webpage/translations") + fs.writeFileSync("dist/webpage/translations/langs.js",`const langs=${JSON.stringify(langobj)};export{langs}`); return gulp .src("translations/*.json") .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist/webpage/translations")); }); - // Task to copy other static assets (e.g., CSS, images) gulp.task("copy-assets", () => { return gulp diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 73a76956..bf72d7d1 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -405,9 +405,7 @@ class Guild extends SnowFlake{ return a.position - b.position; }); } - static generateGuildIcon( - guild: Guild | (invitejson["guild"] & { info: { cdn: string } }) - ){ + static generateGuildIcon(guild: Guild | (invitejson["guild"] & { info: { cdn: string } })){ const divy = document.createElement("div"); divy.classList.add("servernoti"); diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index e960b167..8af5f101 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -1,3 +1,10 @@ +//@ts-ignore +import {langs} from "./translations/langs.js"; +const langmap=new Map(); +for(const lang of Object.keys(langs) as string[]){ + langmap.set(lang,langs[lang]); +} +console.log(langs); type translation={ [key:string]:string|translation }; @@ -8,23 +15,13 @@ class I18n{ static done=new Promise((res2,_reject)=>{ res=res2; }); - static async create(json:translation|string,lang:string){ - if(typeof json === "string"){ - json=await (await fetch(json)).json() as translation; - } + static async create(lang:string){ + + const json=await (await fetch("/translations/"+lang+".json")).json() as translation; const translations:translation[]=[]; - let translation=json[lang]; - if(!translation){ - translation=json[lang[0]+lang[1]]; - if(!translation){ - console.error(lang+" does not exist in the translations"); - translation=json["en"]; - lang="en"; - } - } - translations.push(await this.toTranslation(translation,lang)); + translations.push(json); if(lang!=="en"){ - translations.push(await this.toTranslation(json["en"],"en")) + translations.push(await (await fetch("/translations/en.json")).json() as translation); } this.lang=lang; this.translations=translations; @@ -98,24 +95,17 @@ class I18n{ return msg; } - private static async toTranslation(trans:string|translation,lang:string):Promise{ - if(typeof trans==='string'){ - return this.toTranslation((await (await fetch(trans)).json() as translation)[lang],lang); - }else{ - return trans; - } - } static options(){ - return ["en","ru","tr"] + return [...langmap.keys()].map(e=>e.replace(".json","")); } static setLanguage(lang:string){ if(this.options().indexOf(userLocale)!==-1){ localStorage.setItem("lang",lang); - I18n.create("/translations/en.json",lang); + I18n.create(lang); } } } - +console.log(langmap); let userLocale = navigator.language.slice(0,2) || "en"; if(I18n.options().indexOf(userLocale)===-1){ userLocale="en"; @@ -126,6 +116,6 @@ if(storage){ }else{ localStorage.setItem("lang",userLocale) } -I18n.create("/translations/en.json",userLocale); +I18n.create(userLocale); export{I18n}; diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 7a604ac4..38eec165 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -4,6 +4,64 @@ import { I18n } from "./i18n.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); + +const instancefetch=fetch("/instances.json") + .then(res=>res.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; + } + }[] + )=>{ + instances = json; + if(datalist){ + console.warn(json); + if(instancein && instancein.value === ""){ + instancein.value = json[0].name; + } + for(const instance of json){ + if(instance.display === false){ + continue; + } + const option = document.createElement("option"); + option.disabled = !instance.online; + option.value = instance.name; + if(instance.url){ + stringURLMap.set(option.value, instance.url); + if(instance.urls){ + stringURLsMap.set(instance.url, instance.urls); + } + }else if(instance.urls){ + stringURLsMap.set(option.value, instance.urls); + }else{ + option.disabled = true; + } + if(instance.description){ + option.label = instance.description; + }else{ + option.label = instance.name; + } + datalist.append(option); + } + checkInstance(""); + } + } + ); + +await I18n.done function setTheme(){ let name = localStorage.getItem("theme"); if(!name){ @@ -353,6 +411,7 @@ async function getapiurls(str: string): Promise< } } async function checkInstance(instance?: string){ + await instancefetch; const verify = document.getElementById("verify"); try{ verify!.textContent = I18n.getTranslation("login.checking"); @@ -633,58 +692,4 @@ export function getInstances(){ return instances; } -fetch("/instances.json") - .then(res=>res.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; - } - }[] - )=>{ - instances = json; - if(datalist){ - console.warn(json); - if(instancein && instancein.value === ""){ - instancein.value = json[0].name; - } - for(const instance of json){ - if(instance.display === false){ - continue; - } - const option = document.createElement("option"); - option.disabled = !instance.online; - option.value = instance.name; - if(instance.url){ - stringURLMap.set(option.value, instance.url); - if(instance.urls){ - stringURLsMap.set(instance.url, instance.urls); - } - }else if(instance.urls){ - stringURLsMap.set(option.value, instance.urls); - }else{ - option.disabled = true; - } - if(instance.description){ - option.label = instance.description; - }else{ - option.label = instance.name; - } - datalist.append(option); - } - checkInstance(""); - } - } - ); + diff --git a/src/webpage/register.ts b/src/webpage/register.ts index e9f1ce3b..7a9555cb 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -1,7 +1,7 @@ import { I18n } from "./i18n.js"; import{ checkInstance, adduser }from"./login.js"; import { MarkDown } from "./markdown.js"; - +await I18n.done const registerElement = document.getElementById("register"); if(registerElement){ registerElement.addEventListener("submit", registertry); diff --git a/translations/en.json b/translations/en.json index b11b49c1..fb9123cc 100644 --- a/translations/en.json +++ b/translations/en.json @@ -7,376 +7,374 @@ "locale": "en", "comment":"Don't know how often I'll update this top part lol" }, - "en": { - "reply": "Reply", - "copyrawtext":"Copy raw text", - "copymessageid":"Copy message id", - "permissions":{ - "descriptions":{ - "CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild", - "KICK_MEMBERS": "Allows the user to kick members from the guild", - "BAN_MEMBERS": "Allows the user to ban members from the guild", - "ADMINISTRATOR": "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", - "MANAGE_CHANNELS": "Allows the user to manage and edit channels", - "MANAGE_GUILD": "Allows management and editing of the guild", - "ADD_REACTIONS": "Allows user to add reactions to messages", - "VIEW_AUDIT_LOG": "Allows the user to view the audit log", - "PRIORITY_SPEAKER": "Allows for using priority speaker in a voice channel", - "STREAM": "Allows the user to stream", - "VIEW_CHANNEL": "Allows the user to view the channel", - "SEND_MESSAGES": "Allows user to send messages", - "SEND_TTS_MESSAGES": "Allows the user to send text-to-speech messages", - "MANAGE_MESSAGES": "Allows the user to delete messages that aren't their own", - "EMBED_LINKS": "Allow links sent by this user to auto-embed", - "ATTACH_FILES": "Allows the user to attach files", - "READ_MESSAGE_HISTORY": "Allows user to read the message history", - "MENTION_EVERYONE": "Allows the user to mention everyone", - "USE_EXTERNAL_EMOJIS": "Allows the user to use external emojis", - "VIEW_GUILD_INSIGHTS": "Allows the user to see guild insights", - "CONNECT": "Allows the user to connect to a voice channel", - "SPEAK": "Allows the user to speak in a voice channel", - "MUTE_MEMBERS": "Allows user to mute other members", - "DEAFEN_MEMBERS": "Allows user to deafen other members", - "MOVE_MEMBERS": "Allows the user to move members between voice channels", - "USE_VAD": "Allows users to speak in a voice channel by simply talking", - "CHANGE_NICKNAME": "Allows the user to change their own nickname", - "MANAGE_NICKNAMES": "Allows user to change nicknames of other members", - "MANAGE_ROLES": "Allows user to edit and manage roles", - "MANAGE_WEBHOOKS": "Allows management and editing of webhooks", - "MANAGE_GUILD_EXPRESSIONS": "Allows for managing emoji, stickers, and soundboards", - "USE_APPLICATION_COMMANDS": "Allows the user to use application commands", - "REQUEST_TO_SPEAK": "Allows user to request to speak in stage channel", - "MANAGE_EVENTS": "Allows user to edit and manage events", - "MANAGE_THREADS": "Allows the user to delete and archive threads and view all private threads", - "CREATE_PUBLIC_THREADS": "Allows the user to create public threads", - "CREATE_PRIVATE_THREADS": "Allows the user to create private threads", - "USE_EXTERNAL_STICKERS": "Allows user to use external stickers", - "SEND_MESSAGES_IN_THREADS": "Allows the user to send messages in threads", - "USE_EMBEDDED_ACTIVITIES": "Allows the user to use embedded activities", - "MODERATE_MEMBERS": "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Allows for viewing role subscription insights", - "USE_SOUNDBOARD": "Allows for using soundboard in a voice channel", - "CREATE_GUILD_EXPRESSIONS": "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", - "CREATE_EVENTS": "Allows for creating scheduled events, and editing and deleting those created by the current user.", - "USE_EXTERNAL_SOUNDS": "Allows the usage of custom soundboard sounds from other servers", - "SEND_VOICE_MESSAGES": "Allows sending voice messages", - "SEND_POLLS": "Allows sending polls", - "USE_EXTERNAL_APPS": "Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server." - }, - "readableNames":{ - "CREATE_INSTANT_INVITE": "Create invite", - "KICK_MEMBERS": "Kick members", - "BAN_MEMBERS": "Ban members", - "ADMINISTRATOR": "Administrator", - "MANAGE_CHANNELS": "Manage channels", - "MANAGE_GUILD": "Manage guild", - "ADD_REACTIONS": "Add reactions", - "VIEW_AUDIT_LOG": "View audit log", - "PRIORITY_SPEAKER": "Priority speaker", - "STREAM": "Video", - "VIEW_CHANNEL": "View channels", - "SEND_MESSAGES": "Send messages", - "SEND_TTS_MESSAGES": "Send text-to-speech messages", - "MANAGE_MESSAGES": "Manage messages", - "EMBED_LINKS": "Embed links", - "ATTACH_FILES": "Attach files", - "READ_MESSAGE_HISTORY": "Read message history", - "MENTION_EVERYONE": "Mention @everyone, @here and all roles", - "USE_EXTERNAL_EMOJIS": "Use external emojis", - "VIEW_GUILD_INSIGHTS": "View guild insights", - "CONNECT": "Connect", - "SPEAK": "Speak", - "MUTE_MEMBERS": "Mute members", - "DEAFEN_MEMBERS": "Deafen members", - "MOVE_MEMBERS": "Move members", - "USE_VAD": "Use voice activity detection", - "CHANGE_NICKNAME": "Change nickname", - "MANAGE_NICKNAMES": "Manage nicknames", - "MANAGE_ROLES": "Manage roles", - "MANAGE_WEBHOOKS": "Manage webhooks", - "MANAGE_GUILD_EXPRESSIONS": "Manage expressions", - "USE_APPLICATION_COMMANDS": "Use application commands", - "REQUEST_TO_SPEAK": "Request to speak", - "MANAGE_EVENTS": "Manage events", - "MANAGE_THREADS": "Manage threads", - "CREATE_PUBLIC_THREADS": "Create public threads", - "CREATE_PRIVATE_THREADS": "Create private threads", - "USE_EXTERNAL_STICKERS": "Use external stickers", - "SEND_MESSAGES_IN_THREADS": "Send messages in threads", - "USE_EMBEDDED_ACTIVITIES": "Use activities", - "MODERATE_MEMBERS": "Timeout members", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "View creator monetization analytics", - "USE_SOUNDBOARD": "Use soundboard", - "CREATE_GUILD_EXPRESSIONS": "Create expressions", - "CREATE_EVENTS": "Create events", - "USE_EXTERNAL_SOUNDS": "Use external sounds", - "SEND_VOICE_MESSAGES": "Send voice messages", - "SEND_POLLS": "Create polls", - "USE_EXTERNAL_APPS": "Use external apps" - } + "readableName":"English", + + "reply": "Reply", + "copyrawtext":"Copy raw text", + "copymessageid":"Copy message id", + "permissions":{ + "descriptions":{ + "CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild", + "KICK_MEMBERS": "Allows the user to kick members from the guild", + "BAN_MEMBERS": "Allows the user to ban members from the guild", + "ADMINISTRATOR": "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", + "MANAGE_CHANNELS": "Allows the user to manage and edit channels", + "MANAGE_GUILD": "Allows management and editing of the guild", + "ADD_REACTIONS": "Allows user to add reactions to messages", + "VIEW_AUDIT_LOG": "Allows the user to view the audit log", + "PRIORITY_SPEAKER": "Allows for using priority speaker in a voice channel", + "STREAM": "Allows the user to stream", + "VIEW_CHANNEL": "Allows the user to view the channel", + "SEND_MESSAGES": "Allows user to send messages", + "SEND_TTS_MESSAGES": "Allows the user to send text-to-speech messages", + "MANAGE_MESSAGES": "Allows the user to delete messages that aren't their own", + "EMBED_LINKS": "Allow links sent by this user to auto-embed", + "ATTACH_FILES": "Allows the user to attach files", + "READ_MESSAGE_HISTORY": "Allows user to read the message history", + "MENTION_EVERYONE": "Allows the user to mention everyone", + "USE_EXTERNAL_EMOJIS": "Allows the user to use external emojis", + "VIEW_GUILD_INSIGHTS": "Allows the user to see guild insights", + "CONNECT": "Allows the user to connect to a voice channel", + "SPEAK": "Allows the user to speak in a voice channel", + "MUTE_MEMBERS": "Allows user to mute other members", + "DEAFEN_MEMBERS": "Allows user to deafen other members", + "MOVE_MEMBERS": "Allows the user to move members between voice channels", + "USE_VAD": "Allows users to speak in a voice channel by simply talking", + "CHANGE_NICKNAME": "Allows the user to change their own nickname", + "MANAGE_NICKNAMES": "Allows user to change nicknames of other members", + "MANAGE_ROLES": "Allows user to edit and manage roles", + "MANAGE_WEBHOOKS": "Allows management and editing of webhooks", + "MANAGE_GUILD_EXPRESSIONS": "Allows for managing emoji, stickers, and soundboards", + "USE_APPLICATION_COMMANDS": "Allows the user to use application commands", + "REQUEST_TO_SPEAK": "Allows user to request to speak in stage channel", + "MANAGE_EVENTS": "Allows user to edit and manage events", + "MANAGE_THREADS": "Allows the user to delete and archive threads and view all private threads", + "CREATE_PUBLIC_THREADS": "Allows the user to create public threads", + "CREATE_PRIVATE_THREADS": "Allows the user to create private threads", + "USE_EXTERNAL_STICKERS": "Allows user to use external stickers", + "SEND_MESSAGES_IN_THREADS": "Allows the user to send messages in threads", + "USE_EMBEDDED_ACTIVITIES": "Allows the user to use embedded activities", + "MODERATE_MEMBERS": "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Allows for viewing role subscription insights", + "USE_SOUNDBOARD": "Allows for using soundboard in a voice channel", + "CREATE_GUILD_EXPRESSIONS": "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", + "CREATE_EVENTS": "Allows for creating scheduled events, and editing and deleting those created by the current user.", + "USE_EXTERNAL_SOUNDS": "Allows the usage of custom soundboard sounds from other servers", + "SEND_VOICE_MESSAGES": "Allows sending voice messages", + "SEND_POLLS": "Allows sending polls", + "USE_EXTERNAL_APPS": "Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server." }, - "hideBlockedMessages":"You have this user blocked, click to hide these messages.", - "showBlockedMessages":"You have this user blocked, click to see the $1 blocked {{PLURAL:$1|message|messages}}.", - "deleteConfirm":"Are you sure you want to delete this?", - "yes":"Yes", - "no":"No", - "todayAt":"Today at $1", - "yesterdayAt":"Yesterday at $1", - "otherAt":"$1 at $2", - "botSettings":"Bot Settings", - "uploadPfp":"Upload pfp:", - "uploadBanner":"Upload banner:", - "pronouns":"Pronouns:", - "bio":"Bio:", - "profileColor":"Profile color", - "botGuilds":"Guilds bot is in:", - "leaveGuild":"Leave Guild", - "confirmGuildLeave":"Are you sure you want to leave $1", - "UrlGen":"URL generator", - "typing":"$2 {{PLURAL:$1|is|are}} typing", - "noMessages":"No messages appear to be here, be the first to say something!", - "blankMessage":"Blank Message", - "channel":{ - "copyId":"Copy channel id", - "markRead":"Mark as read", - "settings":"Settings", - "delete":"Delete channel", - "makeInvite":"Make invite", - "settingsFor":"Settings for $1", - "voice":"Voice", - "text":"Text", - "announcement":"Announcements", - "name:":"Name:", - "topic:":"Topic:", - "nsfw:":"NSFW:", - "selectType":"Select channel type", - "selectName":"Name of channel", - "selectCatName":"Name of channel", - "createChannel":"Create channel", - "createCatagory":"Create category" - }, - "switchAccounts":"Switch accounts ⇌", - "accountNotStart":"Account unable to start", - "home":{ - "uptimeStats":"Uptime: \n All time: $1%\nThis week: $2%\nToday: $3%", - "warnOffiline":"Instance is offline, can't connect" - }, - "htmlPages":{ - "idpermissions":"This will allow the bot to:", - "addBot":"Add to server", - "loadingText":"Jank Client is loading", - "loaddesc":"This shouldn't take long", - "switchaccounts":"Switch Accounts", - "instanceField":"Instance:", - "emailField":"Email:", - "pwField":"Password:", - "loginButton":"Login", - "noAccount":"Don't have an account?", - "userField":"Username:", - "pw2Field":"Enter password again:", - "dobField":"Date of birth:", - "createAccount":"Create account", - "alreadyHave":"Already have an account?", - "openClient":"Open Client", - "welcomeJank":"Welcome to Jank Client", - "box1title":"Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:", - "box1Items":"Direct Messaging|Reactions support|Invites|Account switching|User settings|Developer portal|Bot invites|Translation support", - "compatableInstances":"Spacebar-Compatible Instances:", - "box3title":"Contribute to Jank Client", - "box3description":"We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out some typos." - }, - "register":{ - "passwordError:":"Password: $1", - "usernameError":"Username: $1", - "emailError":"Email: $1", - "DOBError":"Date of Birth: $1", - "agreeTOS":"I agree to the [Terms of Service]($1):", - "noTOS":"This instance has no Terms of Service, accept ToS anyways:" - }, - "leaving":"You're leaving Spacebar", - "goingToURL":"You're going to $1. Are you sure you want to go there?", - "goThere":"Go there", - "goThereTrust":"Go there and trust in the future", - "nevermind":"Nevermind", - "submit":"submit", - "guild":{ - "copyId":"Copy guild id", - "markRead":"Mark as read", - "notifications":"Notifications", - "leave":"Leave guild", - "settings":"Settings", - "delete":"Delete guild", - "makeInvite":"Make invite", - "settingsFor":"Settings for $1", - "name:":"Name:", - "topic:":"Topic:", - "icon:":"Icon:", - "overview":"Overview", - "banner:":"Banner:", - "region:":"Region:", - "roles":"Roles", - "selectnoti":"Select notifications type", - "all":"all", - "onlyMentions":"only mentions", - "none":"node", - "confirmLeave":"Are you sure you want to leave?", - "yesLeave":"Yes, I'm sure", - "noLeave":"Nevermind", - "confirmDelete":"Are you sure you want to delete $1?", - "serverName":"Name of server:", - "yesDelete":"Yes, I'm sure", - "noDelete":"Nevermind", - "create":"Create guild", - "loadingDiscovery":"Loading...", - "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}" - }, - "role":{ - "displaySettings":"Display settings", - "name":"Role name:", - "hoisted":"Hoisted:", - "mentionable":"Allow anyone to ping this role:", - "color":"Color", - "remove":"Remove role", - "delete":"Delete Role", - "confirmDelete":"Are you sure you want to delete $1?" - }, - "settings":{ - "unsaved":"Careful, you have unsaved changes", - "save":"Save changes" - }, - "localuser":{ - "settings":"Settings", - "userSettings":"User Settings", - "themesAndSounds":"Themes & Sounds", - "theme:":"Theme", - "notisound":"Notification sound:", - "accentColor":"Accent color:", - "enableEVoice":"Enable experimental Voice support", - "VoiceWarning":"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)", - "updateSettings":"Update Settings", - "swSettings":"Service Worker setting", - "SWOff":"Off", - "SWOffline":"Offline only", - "SWOn":"On", - "clearCache":"Clear cache", - "CheckUpdate":"Check for updates", - "accountSettings":"Account Settings", - "2faDisable":"Disable 2FA", - "badCode":"Invalid code", - "2faEnable":"Enable 2FA", - "2faCode:":"Code:", - "setUp2fa":"2FA Setup", - "badPassword":"Incorrect password", - "setUp2faInstruction":"Copy this secret into your totp(time-based one time password) app", - "2faCodeGive":"Your secret is: $1 and it's 6 digits, with a 30 second token period", - "changeDiscriminator":"Change discriminator", - "newDiscriminator":"New discriminator:", - "changeEmail":"Change email", - "password:":"Password", - "newEmail:":"New email", - "changeUsername":"Change username", - "newUsername":"New username:", - "changePassword":"Change password", - "oldPassword:":"Old password:", - "newPassword:":"New password:", - "PasswordsNoMatch":"Password don't match", - "disableConnection":"This connection has been disabled server-side", - "devPortal":"Developer Portal", - "createApp":"Create application", - "team:":"Team:", - "appName":"Application name:", - "description":"Description:", - "privacyPolcyURL":"Privacy policy URL:", - "TOSURL":"Terms of Service URL:", - "publicAvaliable":"Make bot publicly inviteable?", - "requireCode":"Require code grant to invite the bot?", - "manageBot":"Manage bot", - "addBot":"Add bot", - "confirmAddBot":"Are you sure you want to add a bot to this application? There's no going back.", - "confuseNoBot":"For some reason, this application doesn't have a bot (yet).", - "editingBot":"Editing bot $1", - "botUsername":"Bot username:", - "botAvatar":"Bot avatar:", - "resetToken":"Reset Token", - "confirmReset":"Are you sure you want to reset the bot token? Your bot will stop working until you update it.", - "tokenDisplay":"Token: $1", - "saveToken":"Save token to localStorage", - "noToken":"Don't know token so can't save it to localStorage, sorry", - "advancedBot":"Advanced Bot Settings", - "botInviteCreate":"Bot Invite Creator", - "language":"Language:", - "connections":"Connections" - }, - "message":{ - "reactionAdd":"Add reaction", - "delete":"Delete message", - "edit":"Edit message" - }, - "instanceStats":{ - "name":"Instance stats: $1", - "users":"Registered users: $1", - "servers":"Servers: $1", - "messages":"Messages: $1", - "members":"Members: $1" - }, - "inviteOptions":{ - "title":"Invite People", - "30m":"30 Minutes", - "1h":"1 Hour", - "6h":"6 Hours", - "12h":"12 Hours", - "1d":"1 Day", - "7d":"7 Days", - "30d":"30 Days", - "never":"Never", - "limit":"$1 {{PLURAL:$1|use|uses}}", - "noLimit":"No limit" - }, - "2faCode":"2FA code:", - "invite":{ - "invitedBy":"You've been invited by $1", - "alreadyJoined":"Already joined", - "accept":"Accept", - "noAccount":"Create an account to accept the invite", - "longInvitedBy":"$1 invited you to join $2", - "loginOrCreateAccount":"Login or create an account ⇌", - "joinUsing":"Join using invite", - "inviteLinkCode":"Invite Link/Code" - }, - "replyingTo":"Replying to $1", - "DMs":{ - "copyId":"Copy DM id", - "markRead":"Mark as read", - "close":"Close DM" - }, - "user":{ - "copyId":"Copy user ID", - "online":"Online", - "offline":"Offline", - "message":"Message user", - "block":"Block user", - "unblock":"unblock user", - "friendReq":"Friend request", - "kick":"Kick member", - "ban":"Ban member", - "addRole":"Add roles", - "removeRole":"Remove roles" - }, - "login":{ - "checking":"Checking Instance", - "allGood":"All good", - "invalid":"Invalid Instance, try again", - "waiting":"Waiting to check Instance" - }, - "member":{ - "kick":"Kick $1 from $2", - "reason:":"Reason:", - "ban":"Ban $1 from $2" - }, - "errorReconnect":"Unable to connect to the server, retrying in **$1** seconds...", - "retrying":"Retrying...", - "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." - }, - "ru": "/translations/ru.json", - "tr": "/translations/tr.json" + "readableNames":{ + "CREATE_INSTANT_INVITE": "Create invite", + "KICK_MEMBERS": "Kick members", + "BAN_MEMBERS": "Ban members", + "ADMINISTRATOR": "Administrator", + "MANAGE_CHANNELS": "Manage channels", + "MANAGE_GUILD": "Manage guild", + "ADD_REACTIONS": "Add reactions", + "VIEW_AUDIT_LOG": "View audit log", + "PRIORITY_SPEAKER": "Priority speaker", + "STREAM": "Video", + "VIEW_CHANNEL": "View channels", + "SEND_MESSAGES": "Send messages", + "SEND_TTS_MESSAGES": "Send text-to-speech messages", + "MANAGE_MESSAGES": "Manage messages", + "EMBED_LINKS": "Embed links", + "ATTACH_FILES": "Attach files", + "READ_MESSAGE_HISTORY": "Read message history", + "MENTION_EVERYONE": "Mention @everyone, @here and all roles", + "USE_EXTERNAL_EMOJIS": "Use external emojis", + "VIEW_GUILD_INSIGHTS": "View guild insights", + "CONNECT": "Connect", + "SPEAK": "Speak", + "MUTE_MEMBERS": "Mute members", + "DEAFEN_MEMBERS": "Deafen members", + "MOVE_MEMBERS": "Move members", + "USE_VAD": "Use voice activity detection", + "CHANGE_NICKNAME": "Change nickname", + "MANAGE_NICKNAMES": "Manage nicknames", + "MANAGE_ROLES": "Manage roles", + "MANAGE_WEBHOOKS": "Manage webhooks", + "MANAGE_GUILD_EXPRESSIONS": "Manage expressions", + "USE_APPLICATION_COMMANDS": "Use application commands", + "REQUEST_TO_SPEAK": "Request to speak", + "MANAGE_EVENTS": "Manage events", + "MANAGE_THREADS": "Manage threads", + "CREATE_PUBLIC_THREADS": "Create public threads", + "CREATE_PRIVATE_THREADS": "Create private threads", + "USE_EXTERNAL_STICKERS": "Use external stickers", + "SEND_MESSAGES_IN_THREADS": "Send messages in threads", + "USE_EMBEDDED_ACTIVITIES": "Use activities", + "MODERATE_MEMBERS": "Timeout members", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "View creator monetization analytics", + "USE_SOUNDBOARD": "Use soundboard", + "CREATE_GUILD_EXPRESSIONS": "Create expressions", + "CREATE_EVENTS": "Create events", + "USE_EXTERNAL_SOUNDS": "Use external sounds", + "SEND_VOICE_MESSAGES": "Send voice messages", + "SEND_POLLS": "Create polls", + "USE_EXTERNAL_APPS": "Use external apps" + } + }, + "hideBlockedMessages":"You have this user blocked, click to hide these messages.", + "showBlockedMessages":"You have this user blocked, click to see the $1 blocked {{PLURAL:$1|message|messages}}.", + "deleteConfirm":"Are you sure you want to delete this?", + "yes":"Yes", + "no":"No", + "todayAt":"Today at $1", + "yesterdayAt":"Yesterday at $1", + "otherAt":"$1 at $2", + "botSettings":"Bot Settings", + "uploadPfp":"Upload pfp:", + "uploadBanner":"Upload banner:", + "pronouns":"Pronouns:", + "bio":"Bio:", + "profileColor":"Profile color", + "botGuilds":"Guilds bot is in:", + "leaveGuild":"Leave Guild", + "confirmGuildLeave":"Are you sure you want to leave $1", + "UrlGen":"URL generator", + "typing":"$2 {{PLURAL:$1|is|are}} typing", + "noMessages":"No messages appear to be here, be the first to say something!", + "blankMessage":"Blank Message", + "channel":{ + "copyId":"Copy channel id", + "markRead":"Mark as read", + "settings":"Settings", + "delete":"Delete channel", + "makeInvite":"Make invite", + "settingsFor":"Settings for $1", + "voice":"Voice", + "text":"Text", + "announcement":"Announcements", + "name:":"Name:", + "topic:":"Topic:", + "nsfw:":"NSFW:", + "selectType":"Select channel type", + "selectName":"Name of channel", + "selectCatName":"Name of channel", + "createChannel":"Create channel", + "createCatagory":"Create category" + }, + "switchAccounts":"Switch accounts ⇌", + "accountNotStart":"Account unable to start", + "home":{ + "uptimeStats":"Uptime: \n All time: $1%\nThis week: $2%\nToday: $3%", + "warnOffiline":"Instance is offline, can't connect" + }, + "htmlPages":{ + "idpermissions":"This will allow the bot to:", + "addBot":"Add to server", + "loadingText":"Jank Client is loading", + "loaddesc":"This shouldn't take long", + "switchaccounts":"Switch Accounts", + "instanceField":"Instance:", + "emailField":"Email:", + "pwField":"Password:", + "loginButton":"Login", + "noAccount":"Don't have an account?", + "userField":"Username:", + "pw2Field":"Enter password again:", + "dobField":"Date of birth:", + "createAccount":"Create account", + "alreadyHave":"Already have an account?", + "openClient":"Open Client", + "welcomeJank":"Welcome to Jank Client", + "box1title":"Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:", + "box1Items":"Direct Messaging|Reactions support|Invites|Account switching|User settings|Developer portal|Bot invites|Translation support", + "compatableInstances":"Spacebar-Compatible Instances:", + "box3title":"Contribute to Jank Client", + "box3description":"We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out some typos." + }, + "register":{ + "passwordError:":"Password: $1", + "usernameError":"Username: $1", + "emailError":"Email: $1", + "DOBError":"Date of Birth: $1", + "agreeTOS":"I agree to the [Terms of Service]($1):", + "noTOS":"This instance has no Terms of Service, accept ToS anyways:" + }, + "leaving":"You're leaving Spacebar", + "goingToURL":"You're going to $1. Are you sure you want to go there?", + "goThere":"Go there", + "goThereTrust":"Go there and trust in the future", + "nevermind":"Nevermind", + "submit":"submit", + "guild":{ + "copyId":"Copy guild id", + "markRead":"Mark as read", + "notifications":"Notifications", + "leave":"Leave guild", + "settings":"Settings", + "delete":"Delete guild", + "makeInvite":"Make invite", + "settingsFor":"Settings for $1", + "name:":"Name:", + "topic:":"Topic:", + "icon:":"Icon:", + "overview":"Overview", + "banner:":"Banner:", + "region:":"Region:", + "roles":"Roles", + "selectnoti":"Select notifications type", + "all":"all", + "onlyMentions":"only mentions", + "none":"node", + "confirmLeave":"Are you sure you want to leave?", + "yesLeave":"Yes, I'm sure", + "noLeave":"Nevermind", + "confirmDelete":"Are you sure you want to delete $1?", + "serverName":"Name of server:", + "yesDelete":"Yes, I'm sure", + "noDelete":"Nevermind", + "create":"Create guild", + "loadingDiscovery":"Loading...", + "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}" + }, + "role":{ + "displaySettings":"Display settings", + "name":"Role name:", + "hoisted":"Hoisted:", + "mentionable":"Allow anyone to ping this role:", + "color":"Color", + "remove":"Remove role", + "delete":"Delete Role", + "confirmDelete":"Are you sure you want to delete $1?" + }, + "settings":{ + "unsaved":"Careful, you have unsaved changes", + "save":"Save changes" + }, + "localuser":{ + "settings":"Settings", + "userSettings":"User Settings", + "themesAndSounds":"Themes & Sounds", + "theme:":"Theme", + "notisound":"Notification sound:", + "accentColor":"Accent color:", + "enableEVoice":"Enable experimental Voice support", + "VoiceWarning":"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)", + "updateSettings":"Update Settings", + "swSettings":"Service Worker setting", + "SWOff":"Off", + "SWOffline":"Offline only", + "SWOn":"On", + "clearCache":"Clear cache", + "CheckUpdate":"Check for updates", + "accountSettings":"Account Settings", + "2faDisable":"Disable 2FA", + "badCode":"Invalid code", + "2faEnable":"Enable 2FA", + "2faCode:":"Code:", + "setUp2fa":"2FA Setup", + "badPassword":"Incorrect password", + "setUp2faInstruction":"Copy this secret into your totp(time-based one time password) app", + "2faCodeGive":"Your secret is: $1 and it's 6 digits, with a 30 second token period", + "changeDiscriminator":"Change discriminator", + "newDiscriminator":"New discriminator:", + "changeEmail":"Change email", + "password:":"Password", + "newEmail:":"New email", + "changeUsername":"Change username", + "newUsername":"New username:", + "changePassword":"Change password", + "oldPassword:":"Old password:", + "newPassword:":"New password:", + "PasswordsNoMatch":"Password don't match", + "disableConnection":"This connection has been disabled server-side", + "devPortal":"Developer Portal", + "createApp":"Create application", + "team:":"Team:", + "appName":"Application name:", + "description":"Description:", + "privacyPolcyURL":"Privacy policy URL:", + "TOSURL":"Terms of Service URL:", + "publicAvaliable":"Make bot publicly inviteable?", + "requireCode":"Require code grant to invite the bot?", + "manageBot":"Manage bot", + "addBot":"Add bot", + "confirmAddBot":"Are you sure you want to add a bot to this application? There's no going back.", + "confuseNoBot":"For some reason, this application doesn't have a bot (yet).", + "editingBot":"Editing bot $1", + "botUsername":"Bot username:", + "botAvatar":"Bot avatar:", + "resetToken":"Reset Token", + "confirmReset":"Are you sure you want to reset the bot token? Your bot will stop working until you update it.", + "tokenDisplay":"Token: $1", + "saveToken":"Save token to localStorage", + "noToken":"Don't know token so can't save it to localStorage, sorry", + "advancedBot":"Advanced Bot Settings", + "botInviteCreate":"Bot Invite Creator", + "language":"Language:", + "connections":"Connections" + }, + "message":{ + "reactionAdd":"Add reaction", + "delete":"Delete message", + "edit":"Edit message" + }, + "instanceStats":{ + "name":"Instance stats: $1", + "users":"Registered users: $1", + "servers":"Servers: $1", + "messages":"Messages: $1", + "members":"Members: $1" + }, + "inviteOptions":{ + "title":"Invite People", + "30m":"30 Minutes", + "1h":"1 Hour", + "6h":"6 Hours", + "12h":"12 Hours", + "1d":"1 Day", + "7d":"7 Days", + "30d":"30 Days", + "never":"Never", + "limit":"$1 {{PLURAL:$1|use|uses}}", + "noLimit":"No limit" + }, + "2faCode":"2FA code:", + "invite":{ + "invitedBy":"You've been invited by $1", + "alreadyJoined":"Already joined", + "accept":"Accept", + "noAccount":"Create an account to accept the invite", + "longInvitedBy":"$1 invited you to join $2", + "loginOrCreateAccount":"Login or create an account ⇌", + "joinUsing":"Join using invite", + "inviteLinkCode":"Invite Link/Code" + }, + "replyingTo":"Replying to $1", + "DMs":{ + "copyId":"Copy DM id", + "markRead":"Mark as read", + "close":"Close DM" + }, + "user":{ + "copyId":"Copy user ID", + "online":"Online", + "offline":"Offline", + "message":"Message user", + "block":"Block user", + "unblock":"unblock user", + "friendReq":"Friend request", + "kick":"Kick member", + "ban":"Ban member", + "addRole":"Add roles", + "removeRole":"Remove roles" + }, + "login":{ + "checking":"Checking Instance", + "allGood":"All good", + "invalid":"Invalid Instance, try again", + "waiting":"Waiting to check Instance" + }, + "member":{ + "kick":"Kick $1 from $2", + "reason:":"Reason:", + "ban":"Ban $1 from $2" + }, + "errorReconnect":"Unable to connect to the server, retrying in **$1** seconds...", + "retrying":"Retrying...", + "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." } diff --git a/translations/tr.json b/translations/tr.json index 6422ad9b..6944425c 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -7,368 +7,369 @@ "locale": "tr", "comment": "Sizli bizli" }, - "tr": { - "reply": "Yanıtla", - "copyrawtext": "Ham metni kopyala", - "copymessageid": "Mesaj kimliğini kopyala", - "permissions": { - "descriptions": { - "CREATE_INSTANT_INVITE": "Kullanıcının sunucu için davet oluşturmasına izin verir", - "KICK_MEMBERS": "Kullanıcının sunucudan üyeleri atmasına izin verir", - "BAN_MEMBERS": "Kullanıcının sunucudan üyeleri yasaklamasına izin verir", - "ADMINISTRATOR": "Tüm izinlere izin verir ve kanal izin geçersiz kılmalarını atlar. Bu tehlikeli bir izindir!", - "MANAGE_CHANNELS": "Kullanıcının kanalları yönetmesine ve düzenlemesine izin verir", - "MANAGE_GUILD": "Sunucunun yönetimine ve düzenlenmesine izin verir", - "ADD_REACTIONS": "Kullanıcının mesajlara tepki eklemesine izin verir", - "VIEW_AUDIT_LOG": "Kullanıcının denetim günlüğünü görüntülemesine izin verir", - "PRIORITY_SPEAKER": "Ses kanalında öncelikli konuşmacı kullanımına izin verir", - "STREAM": "Kullanıcının yayın yapmasına izin verir", - "VIEW_CHANNEL": "Kullanıcının kanalı görüntülemesine izin verir", - "SEND_MESSAGES": "Kullanıcının mesaj göndermesine izin verir", - "SEND_TTS_MESSAGES": "Kullanıcının metinden sese mesajlar göndermesine izin verir", - "MANAGE_MESSAGES": "Kullanıcının kendisine ait olmayan mesajları silmesine izin verir", - "EMBED_LINKS": "Bu kullanıcı tarafından gönderilen bağlantıların otomatik olarak gömülmesine izin verir", - "ATTACH_FILES": "Kullanıcının dosya eklemesine izin verir", - "READ_MESSAGE_HISTORY": "Kullanıcının mesaj geçmişini okumasına izin verir", - "MENTION_EVERYONE": "Kullanıcının herkesi etiketlemesine izin verir", - "USE_EXTERNAL_EMOJIS": "Kullanıcının harici emojileri kullanmasına izin verir", - "VIEW_GUILD_INSIGHTS": "Kullanıcının sunucu içgörülerini görmesine izin verir", - "CONNECT": "Kullanıcının bir ses kanalına bağlanmasına izin verir", - "SPEAK": "Kullanıcının bir ses kanalında konuşmasına izin verir", - "MUTE_MEMBERS": "Kullanıcının diğer üyeleri susturmasına izin verir", - "DEAFEN_MEMBERS": "Kullanıcının diğer üyeleri sağırlamasına izin verir", - "MOVE_MEMBERS": "Kullanıcının üyeleri ses kanalları arasında taşımasına izin verir", - "USE_VAD": "Kullanıcıların ses etkinliği ile konuşmasına izin verir", - "CHANGE_NICKNAME": "Kullanıcının kendi takma adını değiştirmesine izin verir", - "MANAGE_NICKNAMES": "Kullanıcının diğer üyelerin takma adlarını değiştirmesine izin verir", - "MANAGE_ROLES": "Kullanıcının rolleri düzenlemesine ve yönetmesine izin verir", - "MANAGE_WEBHOOKS": "Webhook'ların yönetimine ve düzenlenmesine izin verir", - "MANAGE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panolarını yönetmeye izin verir", - "USE_APPLICATION_COMMANDS": "Kullanıcının uygulama komutlarını kullanmasına izin verir", - "REQUEST_TO_SPEAK": "Kullanıcının sahne kanalında konuşma isteğinde bulunmasına izin verir", - "MANAGE_EVENTS": "Kullanıcının etkinlikleri düzenlemesine ve yönetmesine izin verir", - "MANAGE_THREADS": "Kullanıcının konuları silmesine ve arşivlemesine ve tüm özel konuları görüntülemesine izin verir", - "CREATE_PUBLIC_THREADS": "Kullanıcının genel konular oluşturmasına izin verir", - "CREATE_PRIVATE_THREADS": "Kullanıcının özel konular oluşturmasına izin verir", - "USE_EXTERNAL_STICKERS": "Kullanıcının harici çıkartmaları kullanmasına izin verir", - "SEND_MESSAGES_IN_THREADS": "Kullanıcının konularda mesaj göndermesine izin verir", - "USE_EMBEDDED_ACTIVITIES": "Kullanıcının gömülü etkinlikleri kullanmasına izin verir", - "MODERATE_MEMBERS": "Kullanıcının diğer kullanıcıları susturmasına izin verir", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Abonelik içgörülerini görüntülemeye izin verir", - "USE_SOUNDBOARD": "Ses panosunu bir ses kanalında kullanmaya izin verir", - "CREATE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panosu sesleri oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", - "CREATE_EVENTS": "Zamanlanmış etkinlikler oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", - "USE_EXTERNAL_SOUNDS": "Diğer sunuculardan özel ses panosu seslerini kullanmaya izin verir", - "SEND_VOICE_MESSAGES": "Sesli mesajlar göndermeye izin verir", - "SEND_POLLS": "Anketler göndermeye izin verir", - "USE_EXTERNAL_APPS": "Kullanıcı tarafından yüklenen uygulamaların herkese açık yanıtlar göndermesine izin verir. Devre dışı bırakıldığında, kullanıcılar yine de uygulamalarını kullanabilir ancak yanıtlar geçici olacaktır. Bu, sunucuya yüklenmemiş uygulamalar için geçerlidir." - }, - "readableNames": { - "CREATE_INSTANT_INVITE": "Davet oluştur", - "KICK_MEMBERS": "Üyeleri at", - "BAN_MEMBERS": "Üyeleri yasakla", - "ADMINISTRATOR": "Yönetici", - "MANAGE_CHANNELS": "Kanalları yönet", - "MANAGE_GUILD": "Sunucuyu yönet", - "ADD_REACTIONS": "Tepkiler ekle", - "VIEW_AUDIT_LOG": "Denetim günlüğünü görüntüle", - "PRIORITY_SPEAKER": "Öncelikli konuşmacı", - "STREAM": "Video", - "VIEW_CHANNEL": "Kanalları görüntüle", - "SEND_MESSAGES": "Mesaj gönder", - "SEND_TTS_MESSAGES": "Metinden sese mesaj gönder", - "MANAGE_MESSAGES": "Mesajları yönet", - "EMBED_LINKS": "Bağlantıları göm", - "ATTACH_FILES": "Dosyaları ekle", - "READ_MESSAGE_HISTORY": "Mesaj geçmişini oku", - "MENTION_EVERYONE": "@everyone, @here ve tüm rolleri etiketle", - "USE_EXTERNAL_EMOJIS": "Harici emojileri kullan", - "VIEW_GUILD_INSIGHTS": "Sunucu içgörülerini görüntüle", - "CONNECT": "Bağlan", - "SPEAK": "Konuş", - "MUTE_MEMBERS": "Üyeleri sustur", - "DEAFEN_MEMBERS": "Üyeleri sağırla", - "MOVE_MEMBERS": "Üyeleri taşı", - "USE_VAD": "Ses etkinliği algılamayı kullan", - "CHANGE_NICKNAME": "Takma adı değiştir", - "MANAGE_NICKNAMES": "Takma adları yönet", - "MANAGE_ROLES": "Rolleri yönet", - "MANAGE_WEBHOOKS": "Webhook'ları yönet", - "MANAGE_GUILD_EXPRESSIONS": "İfadeleri yönet", - "USE_APPLICATION_COMMANDS": "Uygulama komutlarını kullan", - "REQUEST_TO_SPEAK": "Konuşma isteğinde bulun", - "MANAGE_EVENTS": "Etkinlikleri yönet", - "MANAGE_THREADS": "Konuları yönet", - "CREATE_PUBLIC_THREADS": "Genel konular oluştur", - "CREATE_PRIVATE_THREADS": "Özel konular oluştur", - "USE_EXTERNAL_STICKERS": "Harici çıkartmaları kullan", - "SEND_MESSAGES_IN_THREADS": "Konularda mesaj gönder", - "USE_EMBEDDED_ACTIVITIES": "Etkinlikleri kullan", - "MODERATE_MEMBERS": "Üyeleri zaman aşımına uğrat", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "İçerik üretici gelir analitiğini görüntüle", - "USE_SOUNDBOARD": "Ses panosunu kullan", - "CREATE_GUILD_EXPRESSIONS": "İfadeler oluştur", - "CREATE_EVENTS": "Etkinlikler oluştur", - "USE_EXTERNAL_SOUNDS": "Harici sesleri kullan", - "SEND_VOICE_MESSAGES": "Sesli mesajlar gönder", - "SEND_POLLS": "Anketler oluştur", - "USE_EXTERNAL_APPS": "Harici uygulamaları kullan" - } + "readableName":"Türkçe", + + "reply": "Yanıtla", + "copyrawtext": "Ham metni kopyala", + "copymessageid": "Mesaj kimliğini kopyala", + "permissions": { + "descriptions": { + "CREATE_INSTANT_INVITE": "Kullanıcının sunucu için davet oluşturmasına izin verir", + "KICK_MEMBERS": "Kullanıcının sunucudan üyeleri atmasına izin verir", + "BAN_MEMBERS": "Kullanıcının sunucudan üyeleri yasaklamasına izin verir", + "ADMINISTRATOR": "Tüm izinlere izin verir ve kanal izin geçersiz kılmalarını atlar. Bu tehlikeli bir izindir!", + "MANAGE_CHANNELS": "Kullanıcının kanalları yönetmesine ve düzenlemesine izin verir", + "MANAGE_GUILD": "Sunucunun yönetimine ve düzenlenmesine izin verir", + "ADD_REACTIONS": "Kullanıcının mesajlara tepki eklemesine izin verir", + "VIEW_AUDIT_LOG": "Kullanıcının denetim günlüğünü görüntülemesine izin verir", + "PRIORITY_SPEAKER": "Ses kanalında öncelikli konuşmacı kullanımına izin verir", + "STREAM": "Kullanıcının yayın yapmasına izin verir", + "VIEW_CHANNEL": "Kullanıcının kanalı görüntülemesine izin verir", + "SEND_MESSAGES": "Kullanıcının mesaj göndermesine izin verir", + "SEND_TTS_MESSAGES": "Kullanıcının metinden sese mesajlar göndermesine izin verir", + "MANAGE_MESSAGES": "Kullanıcının kendisine ait olmayan mesajları silmesine izin verir", + "EMBED_LINKS": "Bu kullanıcı tarafından gönderilen bağlantıların otomatik olarak gömülmesine izin verir", + "ATTACH_FILES": "Kullanıcının dosya eklemesine izin verir", + "READ_MESSAGE_HISTORY": "Kullanıcının mesaj geçmişini okumasına izin verir", + "MENTION_EVERYONE": "Kullanıcının herkesi etiketlemesine izin verir", + "USE_EXTERNAL_EMOJIS": "Kullanıcının harici emojileri kullanmasına izin verir", + "VIEW_GUILD_INSIGHTS": "Kullanıcının sunucu içgörülerini görmesine izin verir", + "CONNECT": "Kullanıcının bir ses kanalına bağlanmasına izin verir", + "SPEAK": "Kullanıcının bir ses kanalında konuşmasına izin verir", + "MUTE_MEMBERS": "Kullanıcının diğer üyeleri susturmasına izin verir", + "DEAFEN_MEMBERS": "Kullanıcının diğer üyeleri sağırlamasına izin verir", + "MOVE_MEMBERS": "Kullanıcının üyeleri ses kanalları arasında taşımasına izin verir", + "USE_VAD": "Kullanıcıların ses etkinliği ile konuşmasına izin verir", + "CHANGE_NICKNAME": "Kullanıcının kendi takma adını değiştirmesine izin verir", + "MANAGE_NICKNAMES": "Kullanıcının diğer üyelerin takma adlarını değiştirmesine izin verir", + "MANAGE_ROLES": "Kullanıcının rolleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_WEBHOOKS": "Webhook'ların yönetimine ve düzenlenmesine izin verir", + "MANAGE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panolarını yönetmeye izin verir", + "USE_APPLICATION_COMMANDS": "Kullanıcının uygulama komutlarını kullanmasına izin verir", + "REQUEST_TO_SPEAK": "Kullanıcının sahne kanalında konuşma isteğinde bulunmasına izin verir", + "MANAGE_EVENTS": "Kullanıcının etkinlikleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_THREADS": "Kullanıcının konuları silmesine ve arşivlemesine ve tüm özel konuları görüntülemesine izin verir", + "CREATE_PUBLIC_THREADS": "Kullanıcının genel konular oluşturmasına izin verir", + "CREATE_PRIVATE_THREADS": "Kullanıcının özel konular oluşturmasına izin verir", + "USE_EXTERNAL_STICKERS": "Kullanıcının harici çıkartmaları kullanmasına izin verir", + "SEND_MESSAGES_IN_THREADS": "Kullanıcının konularda mesaj göndermesine izin verir", + "USE_EMBEDDED_ACTIVITIES": "Kullanıcının gömülü etkinlikleri kullanmasına izin verir", + "MODERATE_MEMBERS": "Kullanıcının diğer kullanıcıları susturmasına izin verir", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Abonelik içgörülerini görüntülemeye izin verir", + "USE_SOUNDBOARD": "Ses panosunu bir ses kanalında kullanmaya izin verir", + "CREATE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panosu sesleri oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "CREATE_EVENTS": "Zamanlanmış etkinlikler oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "USE_EXTERNAL_SOUNDS": "Diğer sunuculardan özel ses panosu seslerini kullanmaya izin verir", + "SEND_VOICE_MESSAGES": "Sesli mesajlar göndermeye izin verir", + "SEND_POLLS": "Anketler göndermeye izin verir", + "USE_EXTERNAL_APPS": "Kullanıcı tarafından yüklenen uygulamaların herkese açık yanıtlar göndermesine izin verir. Devre dışı bırakıldığında, kullanıcılar yine de uygulamalarını kullanabilir ancak yanıtlar geçici olacaktır. Bu, sunucuya yüklenmemiş uygulamalar için geçerlidir." }, - "hideBlockedMessages": "Bu kullanıcıyı engellediniz, bu mesajları gizlemek için tıklayın.", - "showBlockedMessages": "Bu kullanıcıyı engellediniz, $1 engellenmiş mesajı görmek için tıklayın.", - "deleteConfirm": "Bunu silmek istediğinizden emin misiniz?", - "yes": "Evet", - "no": "Hayır", - "todayAt": "Bugün saat $1", - "yesterdayAt": "Dün saat $1", - "otherAt": "$1 tarihinde saat $2", - "botSettings": "Bot Ayarları", - "uploadPfp": "Profil resmi yükle:", - "uploadBanner": "Afiş yükle:", - "pronouns": "Zamirler:", - "bio": "Biyografi:", - "profileColor": "Profil rengi", - "botGuilds": "Botun bulunduğu sunucular:", - "leaveGuild": "Sunucudan ayrıl", - "confirmGuildLeave": "$1 sunucusundan ayrılmak istediğinizden emin misiniz", - "UrlGen": "URL oluşturucu", - "typing": "$2 {{PLURAL:$1|yazıyor|yazıyorlar}}", - "noMessages": "Burada mesaj görünmüyor, ilk siz bir şey söyleyin!", - "blankMessage": "Boş Mesaj", - "channel": { - "copyId": "Kanal kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "settings": "Ayarlar", - "delete": "Kanalı sil", - "makeInvite": "Davet oluştur", - "settingsFor": "$1 için ayarlar", - "voice": "Ses", - "text": "Metin", - "announcement": "Duyurular", - "name:": "Ad:", - "topic:": "Konu:", - "nsfw:": "NSFW:", - "selectType": "Kanal türünü seç", - "selectName": "Kanal adı", - "selectCatName": "Kategori adı", - "createChannel": "Kanal oluştur", - "createCatagory": "Kategori oluştur" - }, - "switchAccounts": "Hesapları Değiştir ⇌", - "accountNotStart": "Hesap başlatılamadı", - "home": { - "uptimeStats": "Çalışma Süresi: \n Tüm zamanlar: $1%\nBu hafta: $2%\nBugün: $3%", - "warnOffiline": "Örnek çevrimdışı, bağlanılamıyor" - }, - "htmlPages": { - "idpermissions": "Bu bot şunlara izin verecek:", - "addBot": "Sunucuya ekle", - "loadingText": "Jank Client yükleniyor", - "loaddesc": "Bu uzun sürmemeli", - "switchaccounts": "Hesapları Değiştir", - "instanceField": "Örnek:", - "emailField": "E-posta:", - "pwField": "Parola:", - "loginButton": "Giriş Yap", - "noAccount": "Hesabınız yok mu?", - "userField": "Kullanıcı adı:", - "pw2Field": "Parolayı tekrar girin:", - "dobField": "Doğum tarihi:", - "createAccount": "Hesap oluştur", - "alreadyHave": "Zaten bir hesabınız var mı?", - "openClient": "İstemciyi Aç", - "welcomeJank": "Jank Client'a Hoş Geldiniz", - "box1title": "Jank Client, birçok özellikle mümkün olduğunca iyi olmaya çalışan bir Spacebar uyumlu istemcidir, bunlar arasında:", - "box1Items": "Doğrudan Mesajlaşma|Tepkiler desteği|Davetler|Hesap değiştirme|Kullanıcı ayarları|Geliştirici portalı|Bot davetleri|Çeviri desteği", - "compatableInstances": "Spacebar Uyumlu Örnekler:", - "box3title": "Jank Client'a Katkıda Bulunun", - "box3description": "Her zaman yardımı takdir ederiz, ister hata raporları, ister kod şeklinde olsun, hatta sadece bazı yazım hatalarını işaret etseniz bile." - }, - "register": { - "passwordError:": "Parola: $1", - "usernameError": "Kullanıcı adı: $1", - "emailError": "E-posta: $1", - "DOBError": "Doğum Tarihi: $1", - "agreeTOS": "[Hizmet Şartlarını]($1) kabul ediyorum:", - "noTOS": "Bu örneğin Hizmet Şartları yok, yine de TOS'u kabul et:" - }, - "leaving": "Spacebar'dan ayrılıyorsunuz", - "goingToURL": "$1 adresine gidiyorsunuz. Oraya gitmek istediğinizden emin misiniz?", - "goThere": "Oraya git", - "goThereTrust": "Oraya git ve gelecekte güven", - "nevermind": "Boşver", - "submit": "Gönder", - "guild": { - "copyId": "Sunucu kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "notifications": "Bildirimler", - "leave": "Sunucudan ayrıl", - "settings": "Ayarlar", - "delete": "Sunucuyu sil", - "makeInvite": "Davet oluştur", - "settingsFor": "$1 için ayarlar", - "name:": "Ad:", - "topic:": "Konu:", - "icon:": "Simge:", - "overview": "Genel Bakış", - "banner:": "Afiş:", - "region:": "Bölge:", - "roles": "Roller", - "selectnoti": "Bildirim türünü seç", - "all": "hepsi", - "onlyMentions": "sadece bahsetmeler", - "none": "hiçbiri", - "confirmLeave": "Ayrılmak istediğinizden emin misiniz?", - "yesLeave": "Evet, eminim", - "noLeave": "Boşver", - "confirmDelete": "$1 sunucusunu silmek istediğinizden emin misiniz?", - "serverName": "Sunucu adı:", - "yesDelete": "Evet, eminim", - "noDelete": "Boşver", - "create": "Sunucu oluştur", - "loadingDiscovery": "Yükleniyor...", - "disoveryTitle": "Sunucu keşfi ($1) girdi" - }, - "role": { - "displaySettings": "Görüntü Ayarları", - "name": "Rol adı:", - "hoisted": "Listede göster:", - "mentionable": "Herkes bu rolü etiketleyebilir:", - "color": "Renk", - "remove": "Rolü kaldır", - "delete": "Rolü Sil", - "confirmDelete": "$1 silmek istediğinizden emin misiniz?" - }, - "settings": { - "unsaved": "Dikkatli olun, kaydedilmemiş değişiklikleriniz var", - "save": "Değişiklikleri kaydet" - }, - "localuser": { - "settings": "Ayarlar", - "userSettings": "Kullanıcı Ayarları", - "themesAndSounds": "Temalar & Sesler", - "theme:": "Tema", - "notisound": "Bildirim sesi:", - "accentColor": "Vurgu rengi:", - "enableEVoice": "Deneysel Ses desteğini etkinleştir", - "VoiceWarning": "Bunu etkinleştirmek istediğinizden emin misiniz, bu çok deneysel ve sorunlara neden olabilir. (bu özellik geliştiriciler içindir, ne yaptığınızı bilmiyorsanız lütfen etkinleştirmeyin)", - "updateSettings": "Ayarları Güncelle", - "swSettings": "Servis Çalışanı ayarı", - "SWOff": "Kapalı", - "SWOffline": "Yalnızca Çevrimdışı", - "SWOn": "Açık", - "clearCache": "Önbelleği temizle", - "CheckUpdate": "Güncellemeleri kontrol et", - "accountSettings": "Hesap Ayarları", - "2faDisable": "2FA'yı Devre Dışı Bırak", - "badCode": "Geçersiz kod", - "2faEnable": "2FA'yı Etkinleştir", - "2faCode:": "Kod:", - "setUp2fa": "2FA Kurulumu", - "badPassword": "Yanlış parola", - "setUp2faInstruction": "Bu gizli anahtarı TOTP uygulamanıza kopyalayın", - "2faCodeGive": "Gizli anahtarınız: $1 ve 6 haneli, 30 saniyelik bir token süresi var", - "changeDiscriminator": "Ayırıcıyı değiştir", - "newDiscriminator": "Yeni ayırıcı:", - "changeEmail": "E-postayı değiştir", - "password:": "Parola", - "newEmail:": "Yeni e-posta", - "changeUsername": "Kullanıcı adını değiştir", - "newUsername": "Yeni kullanıcı adı:", - "changePassword": "Parolayı değiştir", - "oldPassword:": "Eski parola:", - "newPassword:": "Yeni parola:", - "PasswordsNoMatch": "Parolalar eşleşmiyor", - "disableConnection": "Bu bağlantı sunucu tarafından devre dışı bırakıldı", - "devPortal": "Geliştirici Portalı", - "createApp": "Uygulama oluştur", - "team:": "Takım:", - "appName": "Uygulama adı:", - "description": "Açıklama:", - "privacyPolcyURL": "Gizlilik politikası URL'si:", - "TOSURL": "Hizmet Şartları URL'si:", - "publicAvaliable": "Botun herkese açık olarak davet edilmesine izin verilsin mi?", - "requireCode": "Botu davet etmek için kod izni gerekiyor mu?", - "manageBot": "Botu yönet", - "addBot": "Bot ekle", - "confirmAddBot": "Bu uygulamaya bir bot eklemek istediğinizden emin misiniz? Geri dönüşü yok.", - "confuseNoBot": "Nedense, bu uygulamanın henüz bir botu yok.", - "editingBot": "Bot $1 düzenleniyor", - "botUsername": "Bot kullanıcı adı:", - "botAvatar": "Bot avatarı:", - "resetToken": "Token'i sıfırla", - "confirmReset": "Bot token'ini sıfırlamak istediğinizden emin misiniz? Botunuz, güncelleyene kadar çalışmayı durduracak.", - "tokenDisplay": "Token: $1", - "saveToken": "Token'i localStorage'a kaydet", - "noToken": "Token'i bilmiyorum, bu yüzden localStorage'a kaydedemem, üzgünüm", - "advancedBot": "Gelişmiş Bot Ayarları", - "botInviteCreate": "Bot Davet Oluşturucu", - "language": "Dil:" - }, - "instanceStats": { - "name": "Örnek istatistikleri: $1", - "users": "Kayıtlı kullanıcılar: $1", - "servers": "Sunucular: $1", - "messages": "Mesajlar: $1", - "members": "Üyeler: $1" - }, - "inviteOptions": { - "title": "Kişileri Davet Et", - "30m": "30 Dakika", - "1h": "1 Saat", - "6h": "6 Saat", - "12h": "12 Saat", - "1d": "1 Gün", - "7d": "7 Gün", - "30d": "30 Gün", - "never": "Asla", - "limit": "$1 kullanım", - "noLimit": "Sınırsız" - }, - "2faCode": "2FA kodu:", - "invite": { - "invitedBy": "$1 sizi davet etti", - "alreadyJoined": "Zaten katıldınız", - "accept": "Kabul et", - "noAccount": "Daveti kabul etmek için bir hesap oluşturun", - "longInvitedBy": "$1 sizi $2'ya katılmaya davet etti", - "loginOrCreateAccount": "Giriş yapın veya hesap oluşturun ⇌", - "joinUsing": "Daveti kullanarak katıl", - "inviteLinkCode": "Davet Bağlantısı/Kodu" - }, - "replyingTo": "$1 yanıtlıyor", - "DMs": { - "copyId": "DM kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "close": "DM'yi kapat" - }, - "user": { - "copyId": "Kullanıcı kimliğini kopyala", - "online": "Çevrimiçi", - "offline": "Çevrimdışı", - "message": "Kullanıcıya mesaj gönder", - "block": "Kullanıcıyı engelle", - "unblock": "Engeli kaldır", - "friendReq": "Arkadaşlık isteği", - "kick": "Üyeyi at", - "ban": "Üyeyi yasakla", - "addRole": "Roller ekle", - "removeRole": "Roller kaldır" - }, - "login": { - "checking": "Örnek kontrol ediliyor", - "allGood": "Her şey yolunda", - "invalid": "Geçersiz örnek, tekrar deneyin", - "waiting": "Örneği kontrol etmek için bekleniyor" - }, - "member": { - "kick": "$1'ı $2'dan at", - "reason:": "Sebep:", - "ban": "$1'ı $2'dan yasakla" - }, - "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", - "retrying": "Yeniden deneniyor...", - "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." - } + "readableNames": { + "CREATE_INSTANT_INVITE": "Davet oluştur", + "KICK_MEMBERS": "Üyeleri at", + "BAN_MEMBERS": "Üyeleri yasakla", + "ADMINISTRATOR": "Yönetici", + "MANAGE_CHANNELS": "Kanalları yönet", + "MANAGE_GUILD": "Sunucuyu yönet", + "ADD_REACTIONS": "Tepkiler ekle", + "VIEW_AUDIT_LOG": "Denetim günlüğünü görüntüle", + "PRIORITY_SPEAKER": "Öncelikli konuşmacı", + "STREAM": "Video", + "VIEW_CHANNEL": "Kanalları görüntüle", + "SEND_MESSAGES": "Mesaj gönder", + "SEND_TTS_MESSAGES": "Metinden sese mesaj gönder", + "MANAGE_MESSAGES": "Mesajları yönet", + "EMBED_LINKS": "Bağlantıları göm", + "ATTACH_FILES": "Dosyaları ekle", + "READ_MESSAGE_HISTORY": "Mesaj geçmişini oku", + "MENTION_EVERYONE": "@everyone, @here ve tüm rolleri etiketle", + "USE_EXTERNAL_EMOJIS": "Harici emojileri kullan", + "VIEW_GUILD_INSIGHTS": "Sunucu içgörülerini görüntüle", + "CONNECT": "Bağlan", + "SPEAK": "Konuş", + "MUTE_MEMBERS": "Üyeleri sustur", + "DEAFEN_MEMBERS": "Üyeleri sağırla", + "MOVE_MEMBERS": "Üyeleri taşı", + "USE_VAD": "Ses etkinliği algılamayı kullan", + "CHANGE_NICKNAME": "Takma adı değiştir", + "MANAGE_NICKNAMES": "Takma adları yönet", + "MANAGE_ROLES": "Rolleri yönet", + "MANAGE_WEBHOOKS": "Webhook'ları yönet", + "MANAGE_GUILD_EXPRESSIONS": "İfadeleri yönet", + "USE_APPLICATION_COMMANDS": "Uygulama komutlarını kullan", + "REQUEST_TO_SPEAK": "Konuşma isteğinde bulun", + "MANAGE_EVENTS": "Etkinlikleri yönet", + "MANAGE_THREADS": "Konuları yönet", + "CREATE_PUBLIC_THREADS": "Genel konular oluştur", + "CREATE_PRIVATE_THREADS": "Özel konular oluştur", + "USE_EXTERNAL_STICKERS": "Harici çıkartmaları kullan", + "SEND_MESSAGES_IN_THREADS": "Konularda mesaj gönder", + "USE_EMBEDDED_ACTIVITIES": "Etkinlikleri kullan", + "MODERATE_MEMBERS": "Üyeleri zaman aşımına uğrat", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "İçerik üretici gelir analitiğini görüntüle", + "USE_SOUNDBOARD": "Ses panosunu kullan", + "CREATE_GUILD_EXPRESSIONS": "İfadeler oluştur", + "CREATE_EVENTS": "Etkinlikler oluştur", + "USE_EXTERNAL_SOUNDS": "Harici sesleri kullan", + "SEND_VOICE_MESSAGES": "Sesli mesajlar gönder", + "SEND_POLLS": "Anketler oluştur", + "USE_EXTERNAL_APPS": "Harici uygulamaları kullan" + } + }, + "hideBlockedMessages": "Bu kullanıcıyı engellediniz, bu mesajları gizlemek için tıklayın.", + "showBlockedMessages": "Bu kullanıcıyı engellediniz, $1 engellenmiş mesajı görmek için tıklayın.", + "deleteConfirm": "Bunu silmek istediğinizden emin misiniz?", + "yes": "Evet", + "no": "Hayır", + "todayAt": "Bugün saat $1", + "yesterdayAt": "Dün saat $1", + "otherAt": "$1 tarihinde saat $2", + "botSettings": "Bot Ayarları", + "uploadPfp": "Profil resmi yükle:", + "uploadBanner": "Afiş yükle:", + "pronouns": "Zamirler:", + "bio": "Biyografi:", + "profileColor": "Profil rengi", + "botGuilds": "Botun bulunduğu sunucular:", + "leaveGuild": "Sunucudan ayrıl", + "confirmGuildLeave": "$1 sunucusundan ayrılmak istediğinizden emin misiniz", + "UrlGen": "URL oluşturucu", + "typing": "$2 {{PLURAL:$1|yazıyor|yazıyorlar}}", + "noMessages": "Burada mesaj görünmüyor, ilk siz bir şey söyleyin!", + "blankMessage": "Boş Mesaj", + "channel": { + "copyId": "Kanal kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "settings": "Ayarlar", + "delete": "Kanalı sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "voice": "Ses", + "text": "Metin", + "announcement": "Duyurular", + "name:": "Ad:", + "topic:": "Konu:", + "nsfw:": "NSFW:", + "selectType": "Kanal türünü seç", + "selectName": "Kanal adı", + "selectCatName": "Kategori adı", + "createChannel": "Kanal oluştur", + "createCatagory": "Kategori oluştur" + }, + "switchAccounts": "Hesapları Değiştir ⇌", + "accountNotStart": "Hesap başlatılamadı", + "home": { + "uptimeStats": "Çalışma Süresi: \n Tüm zamanlar: $1%\nBu hafta: $2%\nBugün: $3%", + "warnOffiline": "Örnek çevrimdışı, bağlanılamıyor" + }, + "htmlPages": { + "idpermissions": "Bu bot şunlara izin verecek:", + "addBot": "Sunucuya ekle", + "loadingText": "Jank Client yükleniyor", + "loaddesc": "Bu uzun sürmemeli", + "switchaccounts": "Hesapları Değiştir", + "instanceField": "Örnek:", + "emailField": "E-posta:", + "pwField": "Parola:", + "loginButton": "Giriş Yap", + "noAccount": "Hesabınız yok mu?", + "userField": "Kullanıcı adı:", + "pw2Field": "Parolayı tekrar girin:", + "dobField": "Doğum tarihi:", + "createAccount": "Hesap oluştur", + "alreadyHave": "Zaten bir hesabınız var mı?", + "openClient": "İstemciyi Aç", + "welcomeJank": "Jank Client'a Hoş Geldiniz", + "box1title": "Jank Client, birçok özellikle mümkün olduğunca iyi olmaya çalışan bir Spacebar uyumlu istemcidir, bunlar arasında:", + "box1Items": "Doğrudan Mesajlaşma|Tepkiler desteği|Davetler|Hesap değiştirme|Kullanıcı ayarları|Geliştirici portalı|Bot davetleri|Çeviri desteği", + "compatableInstances": "Spacebar Uyumlu Örnekler:", + "box3title": "Jank Client'a Katkıda Bulunun", + "box3description": "Her zaman yardımı takdir ederiz, ister hata raporları, ister kod şeklinde olsun, hatta sadece bazı yazım hatalarını işaret etseniz bile." + }, + "register": { + "passwordError:": "Parola: $1", + "usernameError": "Kullanıcı adı: $1", + "emailError": "E-posta: $1", + "DOBError": "Doğum Tarihi: $1", + "agreeTOS": "[Hizmet Şartlarını]($1) kabul ediyorum:", + "noTOS": "Bu örneğin Hizmet Şartları yok, yine de TOS'u kabul et:" + }, + "leaving": "Spacebar'dan ayrılıyorsunuz", + "goingToURL": "$1 adresine gidiyorsunuz. Oraya gitmek istediğinizden emin misiniz?", + "goThere": "Oraya git", + "goThereTrust": "Oraya git ve gelecekte güven", + "nevermind": "Boşver", + "submit": "Gönder", + "guild": { + "copyId": "Sunucu kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "notifications": "Bildirimler", + "leave": "Sunucudan ayrıl", + "settings": "Ayarlar", + "delete": "Sunucuyu sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "name:": "Ad:", + "topic:": "Konu:", + "icon:": "Simge:", + "overview": "Genel Bakış", + "banner:": "Afiş:", + "region:": "Bölge:", + "roles": "Roller", + "selectnoti": "Bildirim türünü seç", + "all": "hepsi", + "onlyMentions": "sadece bahsetmeler", + "none": "hiçbiri", + "confirmLeave": "Ayrılmak istediğinizden emin misiniz?", + "yesLeave": "Evet, eminim", + "noLeave": "Boşver", + "confirmDelete": "$1 sunucusunu silmek istediğinizden emin misiniz?", + "serverName": "Sunucu adı:", + "yesDelete": "Evet, eminim", + "noDelete": "Boşver", + "create": "Sunucu oluştur", + "loadingDiscovery": "Yükleniyor...", + "disoveryTitle": "Sunucu keşfi ($1) girdi" + }, + "role": { + "displaySettings": "Görüntü Ayarları", + "name": "Rol adı:", + "hoisted": "Listede göster:", + "mentionable": "Herkes bu rolü etiketleyebilir:", + "color": "Renk", + "remove": "Rolü kaldır", + "delete": "Rolü Sil", + "confirmDelete": "$1 silmek istediğinizden emin misiniz?" + }, + "settings": { + "unsaved": "Dikkatli olun, kaydedilmemiş değişiklikleriniz var", + "save": "Değişiklikleri kaydet" + }, + "localuser": { + "settings": "Ayarlar", + "userSettings": "Kullanıcı Ayarları", + "themesAndSounds": "Temalar & Sesler", + "theme:": "Tema", + "notisound": "Bildirim sesi:", + "accentColor": "Vurgu rengi:", + "enableEVoice": "Deneysel Ses desteğini etkinleştir", + "VoiceWarning": "Bunu etkinleştirmek istediğinizden emin misiniz, bu çok deneysel ve sorunlara neden olabilir. (bu özellik geliştiriciler içindir, ne yaptığınızı bilmiyorsanız lütfen etkinleştirmeyin)", + "updateSettings": "Ayarları Güncelle", + "swSettings": "Servis Çalışanı ayarı", + "SWOff": "Kapalı", + "SWOffline": "Yalnızca Çevrimdışı", + "SWOn": "Açık", + "clearCache": "Önbelleği temizle", + "CheckUpdate": "Güncellemeleri kontrol et", + "accountSettings": "Hesap Ayarları", + "2faDisable": "2FA'yı Devre Dışı Bırak", + "badCode": "Geçersiz kod", + "2faEnable": "2FA'yı Etkinleştir", + "2faCode:": "Kod:", + "setUp2fa": "2FA Kurulumu", + "badPassword": "Yanlış parola", + "setUp2faInstruction": "Bu gizli anahtarı TOTP uygulamanıza kopyalayın", + "2faCodeGive": "Gizli anahtarınız: $1 ve 6 haneli, 30 saniyelik bir token süresi var", + "changeDiscriminator": "Ayırıcıyı değiştir", + "newDiscriminator": "Yeni ayırıcı:", + "changeEmail": "E-postayı değiştir", + "password:": "Parola", + "newEmail:": "Yeni e-posta", + "changeUsername": "Kullanıcı adını değiştir", + "newUsername": "Yeni kullanıcı adı:", + "changePassword": "Parolayı değiştir", + "oldPassword:": "Eski parola:", + "newPassword:": "Yeni parola:", + "PasswordsNoMatch": "Parolalar eşleşmiyor", + "disableConnection": "Bu bağlantı sunucu tarafından devre dışı bırakıldı", + "devPortal": "Geliştirici Portalı", + "createApp": "Uygulama oluştur", + "team:": "Takım:", + "appName": "Uygulama adı:", + "description": "Açıklama:", + "privacyPolcyURL": "Gizlilik politikası URL'si:", + "TOSURL": "Hizmet Şartları URL'si:", + "publicAvaliable": "Botun herkese açık olarak davet edilmesine izin verilsin mi?", + "requireCode": "Botu davet etmek için kod izni gerekiyor mu?", + "manageBot": "Botu yönet", + "addBot": "Bot ekle", + "confirmAddBot": "Bu uygulamaya bir bot eklemek istediğinizden emin misiniz? Geri dönüşü yok.", + "confuseNoBot": "Nedense, bu uygulamanın henüz bir botu yok.", + "editingBot": "Bot $1 düzenleniyor", + "botUsername": "Bot kullanıcı adı:", + "botAvatar": "Bot avatarı:", + "resetToken": "Token'i sıfırla", + "confirmReset": "Bot token'ini sıfırlamak istediğinizden emin misiniz? Botunuz, güncelleyene kadar çalışmayı durduracak.", + "tokenDisplay": "Token: $1", + "saveToken": "Token'i localStorage'a kaydet", + "noToken": "Token'i bilmiyorum, bu yüzden localStorage'a kaydedemem, üzgünüm", + "advancedBot": "Gelişmiş Bot Ayarları", + "botInviteCreate": "Bot Davet Oluşturucu", + "language": "Dil:" + }, + "instanceStats": { + "name": "Örnek istatistikleri: $1", + "users": "Kayıtlı kullanıcılar: $1", + "servers": "Sunucular: $1", + "messages": "Mesajlar: $1", + "members": "Üyeler: $1" + }, + "inviteOptions": { + "title": "Kişileri Davet Et", + "30m": "30 Dakika", + "1h": "1 Saat", + "6h": "6 Saat", + "12h": "12 Saat", + "1d": "1 Gün", + "7d": "7 Gün", + "30d": "30 Gün", + "never": "Asla", + "limit": "$1 kullanım", + "noLimit": "Sınırsız" + }, + "2faCode": "2FA kodu:", + "invite": { + "invitedBy": "$1 sizi davet etti", + "alreadyJoined": "Zaten katıldınız", + "accept": "Kabul et", + "noAccount": "Daveti kabul etmek için bir hesap oluşturun", + "longInvitedBy": "$1 sizi $2'ya katılmaya davet etti", + "loginOrCreateAccount": "Giriş yapın veya hesap oluşturun ⇌", + "joinUsing": "Daveti kullanarak katıl", + "inviteLinkCode": "Davet Bağlantısı/Kodu" + }, + "replyingTo": "$1 yanıtlıyor", + "DMs": { + "copyId": "DM kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "close": "DM'yi kapat" + }, + "user": { + "copyId": "Kullanıcı kimliğini kopyala", + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "message": "Kullanıcıya mesaj gönder", + "block": "Kullanıcıyı engelle", + "unblock": "Engeli kaldır", + "friendReq": "Arkadaşlık isteği", + "kick": "Üyeyi at", + "ban": "Üyeyi yasakla", + "addRole": "Roller ekle", + "removeRole": "Roller kaldır" + }, + "login": { + "checking": "Örnek kontrol ediliyor", + "allGood": "Her şey yolunda", + "invalid": "Geçersiz örnek, tekrar deneyin", + "waiting": "Örneği kontrol etmek için bekleniyor" + }, + "member": { + "kick": "$1'ı $2'dan at", + "reason:": "Sebep:", + "ban": "$1'ı $2'dan yasakla" + }, + "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", + "retrying": "Yeniden deneniyor...", + "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." + } From a1df59dcccebe219b8735d0cdcabdfffda21fb89 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 14:41:45 -0600 Subject: [PATCH 0058/1330] update docs --- translations.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/translations.md b/translations.md index c7a45acd..66a5546e 100644 --- a/translations.md +++ b/translations.md @@ -1,7 +1,7 @@ # Translations Currently Jank Client is only in english, though I've added support for other languages in the codebase now, if you or someone else wishes to try and help us create these translations it should be rather simple. the translations are stored in `/src/webpage/translations` if you want to help translate a pre-existing translations, you would modify the JSON files there, if you wish to add a new translation that should also be somewhat straight forward -Firstly, modify `en.json` to include your languages file like for example for russian it'd be `"ru": "/translations/ru.json"` so jank client knows where the translation is, then you'd create the file and make sure to include a `"@metadata"` thing at the top to credit yourself, then in the file you'll want to create a property of the object corisponding to the language you're trying to add, for example +include a `"@metadata"` key at the top to credit yourself, then in the file you'll want to create a property of the object corisponding to the language you're trying to add, for example ```json { "@metadata": { @@ -11,8 +11,6 @@ Firstly, modify `en.json` to include your languages file like for example for ru "locale": "ru", "comment":"" }, - "ru":{ - } } ``` Then to add the actual translations, just take the english version and in the same structure add your language, you must keep the left side, as that's what jank needs to know what the translation is. From 9dc7637ee9a1cf4b933970e527e19785de73c395 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 14:42:39 -0600 Subject: [PATCH 0059/1330] update qqq --- translations/qqq.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/qqq.json b/translations/qqq.json index bc17f5e3..70734028 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -8,7 +8,7 @@ "comment":"Don't know how often I'll update this top part lol" }, "qqq":{ - "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then for each language it'll have a string that either contains the translation in json, or points to the translation as a string, for example you'll see \"ru\": \"/translations/ru.json\" in en.json as en.json does not contain the russian translation directly, instead it says that the russian translation is at that location. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", + "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", "permissions":{ "description":"This object contains the descriptions of permisions, along with their names." From d38276b028ee12b8d0a7115b642d715594b7ed43 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 12 Nov 2024 14:54:18 -0600 Subject: [PATCH 0060/1330] fixes and improved language selector --- src/webpage/i18n.ts | 3 ++- src/webpage/index.html | 4 ++-- src/webpage/index.ts | 7 ------- src/webpage/localuser.ts | 4 ++-- src/webpage/login.ts | 45 ++++++++++++++++++++-------------------- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 8af5f101..742ba328 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -25,6 +25,7 @@ class I18n{ } this.lang=lang; this.translations=translations; + res(); } static getTranslation(msg:string,...params:string[]):string{ @@ -118,4 +119,4 @@ if(storage){ } I18n.create(userLocale); -export{I18n}; +export{I18n,langmap}; diff --git a/src/webpage/index.html b/src/webpage/index.html index d2aa1c28..f4ac781c 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -16,7 +16,7 @@ - +
@@ -98,5 +98,5 @@

Server Name

- + diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 27ec5717..6b1cb436 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -7,13 +7,6 @@ import{ File }from"./file.js"; import { I18n } from "./i18n.js"; (async ()=>{ - async function waitForLoad(): Promise{ - return new Promise(resolve=>{ - document.addEventListener("DOMContentLoaded", _=>resolve()); - }); - } - - await waitForLoad(); await I18n.done const users = getBulkUsers(); if(!users.currentuser){ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 7dede8ec..9bb7a3f4 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -12,7 +12,7 @@ import{ getTextNodeAtPosition, MarkDown, saveCaretPosition }from"./markdown.js"; import { Bot } from "./bot.js"; import { Role } from "./role.js"; import { VoiceFactory } from "./voice.js"; -import { I18n } from "./i18n.js"; +import { I18n, langmap } from "./i18n.js"; const wsCodesRetry = new Set([4000,4001,4002, 4003, 4005, 4007, 4008, 4009]); @@ -1453,7 +1453,7 @@ class Localuser{ security.addSelect(I18n.getTranslation("localuser.language"),(e)=>{ I18n.setLanguage(I18n.options()[e]); - },I18n.options(),{ + },[...langmap.values()],{ defaultIndex:I18n.options().indexOf(I18n.lang) }); { diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 38eec165..4705b033 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -4,7 +4,27 @@ import { I18n } from "./i18n.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); - +let instances: +| { +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; +}; +}[] +| null; +const datalist = document.getElementById("instances"); +console.warn(datalist); const instancefetch=fetch("/instances.json") .then(res=>res.json()) .then( @@ -70,25 +90,7 @@ function setTheme(){ } document.body.className = name + "-theme"; } -let instances: -| { -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; -}; -}[] -| null; + (async ()=>{ await I18n.done @@ -686,8 +688,7 @@ export{ adduser, }; -const datalist = document.getElementById("instances"); -console.warn(datalist); + export function getInstances(){ return instances; } From 620c1d7956b07c465a07ca829ee857c5d614b1af Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 14 Nov 2024 08:19:28 -0600 Subject: [PATCH 0061/1330] fix qqq.json --- translations/qqq.json | 109 +++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/translations/qqq.json b/translations/qqq.json index 70734028..67c73083 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -7,59 +7,58 @@ "locale": "en", "comment":"Don't know how often I'll update this top part lol" }, - "qqq":{ - "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", - "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", - "permissions":{ - "description":"This object contains the descriptions of permisions, along with their names." - }, - "channel":{ - "description":"This object contains strings related to channels, which are analogous to discord channels." - }, - "home":{ - "description":"This contains the dynamic text for the homepage" - }, - "htmlPages":{ - "description":"This contains much of the text for the html pages so that they can have translations applied as well.", - "box1Items":"this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" - }, - "register":{ - "description":"This contains the dynamic text for the register page", - "agreeTOS":"uses MarkDown" - }, - "guild":{ - "description":"This object contains strings related to guilds, which are analogous to discord guilds." - }, - "role":{ - "description":"This object contains some strings related to roles, which are analogous to discord roles." - }, - "settings":{ - "description":"This object contains some strings related to settings menu which are fairly generic" - }, - "localuser":{ - "description":"This object contains strings related to the logged in user, which is mostly the settings for the user" - }, - "instanceStats":{ - "description":"This object contains strings related to instance stats" - }, - "inviteOptions":{ - "description":"This object contains strings for the invite creator" - }, - "invite":{ - "description":"This object contains strings for invites outside of their creation" - }, - "DMs":{ - "description":"This object contains strings related to dirrect messaging" - }, - "user":{ - "description":"This object contains strings related to users" - }, - "login":{ - "description":"This object contains strings related to the login page" - }, - "member":{ - "description":"This object contains strings related to guild members" - }, - "errorReconnect":"Uses MarkDown" - } + "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", + "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", + "permissions":{ + "description":"This object contains the descriptions of permisions, along with their names." + }, + "channel":{ + "description":"This object contains strings related to channels, which are analogous to discord channels." + }, + "home":{ + "description":"This contains the dynamic text for the homepage" + }, + "htmlPages":{ + "description":"This contains much of the text for the html pages so that they can have translations applied as well.", + "box1Items":"this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" + }, + "register":{ + "description":"This contains the dynamic text for the register page", + "agreeTOS":"uses MarkDown" + }, + "guild":{ + "description":"This object contains strings related to guilds, which are analogous to discord guilds." + }, + "role":{ + "description":"This object contains some strings related to roles, which are analogous to discord roles." + }, + "settings":{ + "description":"This object contains some strings related to settings menu which are fairly generic" + }, + "localuser":{ + "description":"This object contains strings related to the logged in user, which is mostly the settings for the user" + }, + "instanceStats":{ + "description":"This object contains strings related to instance stats" + }, + "inviteOptions":{ + "description":"This object contains strings for the invite creator" + }, + "invite":{ + "description":"This object contains strings for invites outside of their creation" + }, + "DMs":{ + "description":"This object contains strings related to dirrect messaging" + }, + "user":{ + "description":"This object contains strings related to users" + }, + "login":{ + "description":"This object contains strings related to the login page" + }, + "member":{ + "description":"This object contains strings related to guild members" + }, + "errorReconnect":"Uses MarkDown" + } From 6d44b63e321a3f327f9f3aa7c01cb30db705e056 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 15 Nov 2024 09:26:30 -0600 Subject: [PATCH 0062/1330] qqq update --- translations/qqq.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/qqq.json b/translations/qqq.json index 67c73083..1ec1ac5d 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -7,7 +7,7 @@ "locale": "en", "comment":"Don't know how often I'll update this top part lol" }, - "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty.", + "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty. readableName is needed for jank client to know to pick up a language in the settings.", "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", "permissions":{ "description":"This object contains the descriptions of permisions, along with their names." From 07b61303a47f62b3c58e12c7bc03b3ede10d8752 Mon Sep 17 00:00:00 2001 From: MathMan05 <73901602+MathMan05@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:22:47 -0600 Subject: [PATCH 0063/1330] Update translations.md --- translations.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/translations.md b/translations.md index 66a5546e..340ff870 100644 --- a/translations.md +++ b/translations.md @@ -1,10 +1,8 @@ # Translations -Currently Jank Client is only in english, though I've added support for other languages in the codebase now, if you or someone else wishes to try and help us create these translations it should be rather simple. -the translations are stored in `/src/webpage/translations` if you want to help translate a pre-existing translations, you would modify the JSON files there, if you wish to add a new translation that should also be somewhat straight forward -include a `"@metadata"` key at the top to credit yourself, then in the file you'll want to create a property of the object corisponding to the language you're trying to add, for example +the translations are stored in `/src/webpage/translations` in this format. ```json { - "@metadata": { + "@metadata": { "authors": [ ], "last-updated": "XXXX/XX/XX", @@ -13,10 +11,9 @@ include a `"@metadata"` key at the top to credit yourself, then in the file you' }, } ``` -Then to add the actual translations, just take the english version and in the same structure add your language, you must keep the left side, as that's what jank needs to know what the translation is. - -Thank you so much for contributing another lanuage, or even just parts, as jank will fall back to the english translation if your translation has gaps in it, so it's not all or nothing. +## I want to help translate this +Please go to [https://translatewiki.net/wiki/Translating:JankClient](https://translatewiki.net/wiki/Translating:JankClient) to help translate this project ## What is the format? It's the same format found [here](https://github.com/wikimedia/jquery.i18n#message-file-format), though we are not using jquery, and you might notice some of the strings use markdown, but most do not. From 0aff5471d5f06251e97195b1a8567042812e7634 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 15 Nov 2024 11:24:51 -0600 Subject: [PATCH 0064/1330] Add russian translation Thank you so much to StealthTheAngryBird for making this for me. --- translations/ru.json | 335 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 331 insertions(+), 4 deletions(-) diff --git a/translations/ru.json b/translations/ru.json index bbc86f1a..bff4e7fe 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -1,12 +1,339 @@ { "@metadata": { "authors": [ + "StealthTheAngryBird" ], "last-updated": "2024/15/24", "locale": "ru", - "comment":"I need some help with this :P" + "comment":"Русский перевод Jank Client" }, - "ru": { - - } + "reply": "Ответить", + "copyrawtext":"Скопировать текст", + "copymessageid":"Копировать ID сообщения", + "permissions":{ + "descriptions":{ + "CREATE_INSTANT_INVITE": "Позволяет участникам приглашать новых участников в эту гильдию", + "KICK_MEMBERS": "Позволяет участникам удалять других участников из этой гильдии", + "BAN_MEMBERS": "Позволяет участникам банить других участников в этой гильдии", + "ADMINISTRATOR": "Участники с этим правом имеют все права и обходят особые права и ограничения каналов. Давать это право опасно!", + "MANAGE_CHANNELS": "Позволяет участникам управлять каналами и редактировать их", + "MANAGE_GUILD": "Позволяет управление и изменение этой гильдии", + "ADD_REACTIONS": "Позволяет участникам добавлять реакции на сообщения", + "VIEW_AUDIT_LOG": "Позволяет участнику просматривать журнал аудита", + "PRIORITY_SPEAKER": "Даёт участникам больше шансов быть услышанными в голосовых каналах", + "STREAM": "Позволяет участникам показывать свой экран", + "VIEW_CHANNEL": "Позволяет участникам просматривать каналы (кроме приватных) по умолчанию", + "SEND_MESSAGES": "Позволяет участникам отправлять сообщения в текстовых каналах", + "SEND_TTS_MESSAGES": "Даёт участникам возможность отправлять TTS-сообщения", + "MANAGE_MESSAGES": "Позволяет пользователям удалять сообщения других пользователей", + "EMBED_LINKS": "Позволяет отображать контент ссылок в текстовых каналах", + "ATTACH_FILES": "Позволяет пользователям прикреплять файлы к сообщениям", + "READ_MESSAGE_HISTORY": "Позволяет пользователям читать историю сообщений", + "MENTION_EVERYONE": "Позволяет пользователю упоминать всех", + "USE_EXTERNAL_EMOJIS": "Позволяет пользователям использовать эмодзи из других гильдий", + "VIEW_GUILD_INSIGHTS": "Позволяет пользователям просматривать аналитику гильдии", + "CONNECT": "Позволяет пользователям подключаться к голосовым каналам", + "SPEAK": "Позволяет пользователям говорить в голосовых каналах", + "MUTE_MEMBERS": "Позволяет пользователям выключать микрофон другим пользователям", + "DEAFEN_MEMBERS": "Позволяет пользователям заглушать других пользователей", + "MOVE_MEMBERS": "Позволяет пользователям перемещать других пользователей между каналами", + "USE_VAD": "Позволяет пользователям говорить в голосовых каналах просто разговаривая", + "CHANGE_NICKNAME": "Позволяет пользователям изменять их собственные никнеймы", + "MANAGE_NICKNAMES": "Позволяет пользователям изменять никнеймы другим пользователям", + "MANAGE_ROLES": "Позволяет пользователям редактировать и управлять ролями", + "MANAGE_WEBHOOKS": "Даёт управление и изменение вебхуков", + "MANAGE_GUILD_EXPRESSIONS": "Даёт управление эмодзи, стикерами и звуковыми панелями", + "USE_APPLICATION_COMMANDS": "Позволяет пользователям исользовать команды приложений", + "REQUEST_TO_SPEAK": "Позволяет пользователям говорить в канале-трибуне", + "MANAGE_EVENTS": "Позволяет пользователям редактировать и управлять событиями", + "MANAGE_THREADS": "Позволяет пользователям удалять, архивировать, а также просматривать приватные ветки", + "CREATE_PUBLIC_THREADS": "Позволяет пользователям создавать публичные ветки", + "CREATE_PRIVATE_THREADS": "Позволяет пользователям создавать приватные ветки", + "USE_EXTERNAL_STICKERS": "Позволяет пользователям использовать внешние стикеры", + "SEND_MESSAGES_IN_THREADS": "Позволяет пользователям отправлять сообщения в ветках", + "USE_EMBEDDED_ACTIVITIES": "Позволяет пользователям использовать активности", + "MODERATE_MEMBERS": "Позволяет пользователям отключать других пользователей, чтобы запретить им отправлять или реагировать на сообщения в чате и темах, а также разговаривать в голосовых каналах и на трибунах.", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Даёт просмотр аналитики подписок на роли", + "USE_SOUNDBOARD": "Позволяет пользователям использовать звуки из звуковой панели в голосовых каналах", + "CREATE_GUILD_EXPRESSIONS": "Позволяет добавлять эмодзи, стикеры и звуки, а также удалять и изменять те, что были соданы другими пользователями.", + "CREATE_EVENTS": "Позволяет проводить события, а также удалять и изменять те, что были соданы другими пользователями.", + "USE_EXTERNAL_SOUNDS": "Даёт использование звуков из других серверов", + "SEND_VOICE_MESSAGES": "Позволяет отправлять голосовые сообщения", + "SEND_POLLS": "Позволяет создавать голосования", + "USE_EXTERNAL_APPS": "Позволяет установленным пользователем приложениям отправлять публичные ответы. Если отключено, пользователи по-прежнему смогут использовать свои приложения, но ответы будут эфемерными. Это применимо только к приложениям, которые не установлены на сервере." + }, + "readableNames":{ + "CREATE_INSTANT_INVITE": "Создание приглашения", + "KICK_MEMBERS": "Выгонять участников", + "BAN_MEMBERS": "Банить участников", + "ADMINISTRATOR": "Администратор", + "MANAGE_CHANNELS": "Управлять каналами", + "MANAGE_GUILD": "Управлять гильдией", + "ADD_REACTIONS": "Добавлять реакции", + "VIEW_AUDIT_LOG": "Просматривать журнал аудита", + "PRIORITY_SPEAKER": "Приоритетный режим", + "STREAM": "Видео", + "VIEW_CHANNEL": "Просматривать каналы", + "SEND_MESSAGES": "Отправлять сообщения", + "SEND_TTS_MESSAGES": "Отправлять TTS-сообщения", + "MANAGE_MESSAGES": "Управлять сообщениями", + "EMBED_LINKS": "Вставлять контент ссылок", + "ATTACH_FILES": "Прикреплять файлы", + "READ_MESSAGE_HISTORY": "Читать историю сообщений", + "MENTION_EVERYONE": "Упоминать @everyone, @here и все роли", + "USE_EXTERNAL_EMOJIS": "Использовать сторонние эмодзи", + "VIEW_GUILD_INSIGHTS": "Просмотр аналитики гильдии", + "CONNECT": "Подключаться", + "SPEAK": "Говорить", + "MUTE_MEMBERS": "Отключать участникам микрофон", + "DEAFEN_MEMBERS": "Отключать участникам звук", + "MOVE_MEMBERS": "Перемещать участников", + "USE_VAD": "Использовать режим активации по голосу", + "CHANGE_NICKNAME": "Изменять никнейм", + "MANAGE_NICKNAMES": "Управлять никнеймами", + "MANAGE_ROLES": "Управлять ролями", + "MANAGE_WEBHOOKS": "Управлять вебхуками (webhooks)", + "MANAGE_GUILD_EXPRESSIONS": "Управлять выражениями", + "USE_APPLICATION_COMMANDS": "Использовать команды приложения", + "REQUEST_TO_SPEAK": "Попросить вступить", + "MANAGE_EVENTS": "Управлять событиями", + "MANAGE_THREADS": "Управлять ветками", + "CREATE_PUBLIC_THREADS": "Создавать публичные ветки", + "CREATE_PRIVATE_THREADS": "Создавать приватные ветки", + "USE_EXTERNAL_STICKERS": "Использовать сторонние стикеры", + "SEND_MESSAGES_IN_THREADS": "Отправлять сообщения в ветках", + "USE_EMBEDDED_ACTIVITIES": "Использовать активности", + "MODERATE_MEMBERS": "Отправлять участников подумать о своём поведении", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Просматривать аналитику монетизации создателей", + "USE_SOUNDBOARD": "Использовать звуковую панель", + "CREATE_GUILD_EXPRESSIONS": "Создавать выражения", + "CREATE_EVENTS": "Создавать события", + "USE_EXTERNAL_SOUNDS": "Использовать внешние звуки", + "SEND_VOICE_MESSAGES": "Отправлять голосовые сообщения", + "SEND_POLLS": "Создавать голосования", + "USE_EXTERNAL_APPS": "Использовать внешние приложения" + } + }, + "hideBlockedMessages":"Вы заблокировали этого пользователя, нажмите чтобы скрыть эти сообщения.", + "showBlockedMessages":"Вы заблокировали этого пользователя, нажмите чтобы просмотреть $1 $2 {{plural:$2|заблокированное|заблокированных}} {{PLURAL:$1|сообщение|сообщений}}.", + "deleteConfirm":"Вы точно уверены в том, что хотите удалить это?", + "yes":"Да", + "no":"Нет", + "todayAt":"Сегодня, в $1", + "yesterdayAt":"Вчера, в $1", + "otherAt":"$1 в $2", + "botSettings":"Настройки бота", + "uploadPfp":"Загрузить картинку профиля:", + "uploadBanner":"Загрузить баннер:", + "pronouns":"Местоимения:", + "bio":"Обо мне:", + "profileColor":"Цвет профиля", + "botGuilds":"Гильдии, в которых находится бот:", + "leaveGuild":"Покинуть гильдию", + "confirmGuildLeave":"Вы уверены, что хотите покинуть $1", + "UrlGen":"Генератор URL", + "typing":"$2 {{PLURAL:$1|печатают|печатает}}", + "noMessages":"Видимо здесь пока что нет сообщений, будьте первыми, кто напишет сюда!", + "blankMessage":"Пустое сообщение", + "channel":{ + "copyId":"Копировать ID канала", + "markRead":"Пометить как прочитанное", + "settings":"Настройки", + "delete":"Удалить канал", + "makeInvite":"Создать приглашение", + "settingsFor":"Настройки $1", + "voice":"Голос", + "text":"Текст", + "announcement":"Объявление", + "name:":"Название канала:", + "topic:":"Тема канала:", + "nsfw:":"Возрастное ограничение:", + "selectType":"Выберите тип канала", + "selectName":"Название канала", + "selectCatName":"Название канала", + "createChannel":"Создать канал", + "createCatagory":"Создать категорию" + }, + "switchAccounts":"Переключение между аккаунтами ⇌", + "accountNotStart":"Аккаунт не может быть запущен", + "home":{ + "uptimeStats":"Время онлайн: \n Всё время: $1\nЭта неделя: $2\nЭтот день: $3", + "warnOffiline":"Инстанция не в сети, невозможно подключиться" + }, + "register":{ + "passwordError:":"Пароль: $1", + "usernameError":"Имя пользователя: $1", + "emailError":"Эл. почта: $1", + "DOBError":"Дата рождения: $1", + "agreeTOS":"Я соглашаюсь с [Условиями Использования]($1):", + "noTOS":"На этой инстанции нет Условий Использования, но всё равно примите их:" + }, + "leaving":"Вы покидаете Spacebar", + "goingToURL":"Вы переходите на сайт $1. Вы уверены в том, что хотите перейти туда?", + "goThere":"Перейти", + "goThereTrust":"Перейти и доверять в будущем", + "nevermind":"Не сейчас", + "submit":"подать", + "guild":{ + "copyId":"Копировать ID гильдии", + "markRead":"Пометить как прочитанное", + "notifications":"Уведомления", + "leave":"Покинуть гильдию", + "settings":"Настройки", + "delete":"Удалить гильдию", + "makeInvite":"Создать приглашение", + "settingsFor":"Настройки $1", + "name:":"Название:", + "topic:":"Тема:", + "icon:":"Иконка:", + "overview":"Обзор", + "banner:":"Баннер:", + "region:":"Регион:", + "roles":"Роли", + "selectnoti":"Выбрать тип уведомлений", + "all":"все", + "onlyMentions":"только упоминания", + "none":"никакие", + "confirmLeave":"Вы уверены, что хотите выйти?", + "yesLeave":"Да, я уверен", + "noLeave":"Не сейчас", + "confirmDelete":"Вы уверены, что хотите удалить $1?", + "serverName":"Название сервера:", + "yesDelete":"Да, я уверен", + "noDelete":"Не сейчас", + "create":"Создать гильдию", + "loadingDiscovery":"Загрузка...", + "disoveryTitle":"Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}" + }, + "role":{ + "displaySettings":"Настройки отображения", + "name":"Название роли:", + "hoisted":"Отображать раздельно:", + "mentionable":"Разрешить всем упоминать эту роль:", + "color":"Цвет", + "remove":"Убрать роль", + "delete":"Удалить роль", + "confirmDelete":"Вы уверены в том, что хотите удалить роль $1?" + }, + "settings":{ + "unsaved":"Осторожно, вы не сохранили изменения", + "save":"Сохранить изменения" + }, + "localuser":{ + "settings":"Настройки", + "userSettings":"Настройки пользователя", + "themesAndSounds":"Темы и звуки", + "theme:":"Тема", + "notisound":"Звук уведомления:", + "accentColor":"Акцентный цвет:", + "enableEVoice":"Включить экспериментальную поддержку голосовых каналов", + "VoiceWarning":"Вы точно хотите включить это? Эта функция очень экспериментальная и может вызвать проблемы. (эта функция создана для разработчиков, пожалуйста не включайте если не знаете, что делаете)", + "updateSettings":"Обновить настройки", + "swSettings":"Service Worker setting", + "SWOff":"Выкл.", + "SWOffline":"Только вне сети", + "SWOn":"Вкл.", + "clearCache":"Очистить кеш", + "CheckUpdate":"Проверить наличие обновлений", + "accountSettings":"Настройки аккаунта", + "2faDisable":"Выключить двухфакторную аутентификацию", + "badCode":"Неверный код", + "2faEnable":"Включить двухфакторную аутентификацию", + "2faCode:":"Код:", + "setUp2fa":"Настройка двухфакторной аутентификации", + "badPassword":"Неверный пароль", + "setUp2faInstruction":"Скопируйте этот код в в приложение одноразового пароля по времени", + "2faCodeGive":"Вот ваш шестизначный код: $1. Он имеет ограничение в 30 секунд", + "changeDiscriminator":"Сменить дискриминатор", + "newDiscriminator":"Новый дискриминатор:", + "changeEmail":"Сменить адрес эл. почты", + "password:":"Пароль", + "newEmail:":"Новый адрес эл. почты", + "changeUsername":"Сменить имя пользователя", + "newUsername":"Новое имя пользователя:", + "changePassword":"Сменить пароль", + "oldPassword:":"Старый пароль:", + "newPassword:":"Новый пароль:", + "PasswordsNoMatch":"Пароли не совпадают", + "disableConnection":"Это подключение было отключено на стороне сервера", + "devPortal":"Портал разработчика", + "createApp":"Создать приложение", + "team:":"Команда:", + "appName":"Название приложения:", + "description":"Описание:", + "privacyPolcyURL":"URL Политики безопасности:", + "TOSURL":"URL Условий Использования:", + "publicAvaliable":"Сделать бота публично приглашаемым?", + "requireCode":"Требовать код для приглашения бота?", + "manageBot":"Управлять ботом", + "addBot":"Добавить бота", + "confirmAddBot":"Вы уверены в том, что хотите добавить бота в это приложение? Пути назад нет.", + "confuseNoBot":"По какой-то причине, у этого приложения нет бота (пока что).", + "editingBot":"Изменение бота $1", + "botUsername":"Название бота:", + "botAvatar":"Аватар бота:", + "resetToken":"Сбросить токен", + "confirmReset":"Вы уверены в том, что хотите сбросить токен бота? Ваш бот перестанет работать до того как вы его обновите.", + "tokenDisplay":"Токен: $1", + "saveToken":"Сохранить токен в localStorage", + "noToken":"Токен неизвестен, так что его нельзя сохранить localStorage, извините", + "advancedBot":"Расширенные настройки бота", + "botInviteCreate":"Создатель приглашения бота" + }, + "inviteOptions":{ + "title":"Пригласить людей", + "30m":"30 минут", + "1h":"1 час", + "6h":"6 часов", + "12h":"12 часов", + "1d":"1 день", + "7d":"7 дней", + "30d":"30 дней", + "never":"Никогда", + "limit":"$1 {{PLURAL:$1|использование|использования|использований}}", + "noLimit":"Нет ограничений" + }, + "invite":{ + "invitedBy":"Вас пригласил пользователь $1", + "alreadyJoined":"Уже в гильдии", + "accept":"Принять", + "noAccount":"Создайте аккаунт, чтобы принять это приглашение", + "longInvitedBy":"Пользователь $1 пригласил вас на $2", + "loginOrCreateAccount":"Войдите или создайте аккаунт ⇌", + "joinUsing":"Присоединиться с помощью приглашения", + "inviteLinkCode":"Ссылка-приглашение/Код" + }, + "replyingTo":"В ответ $1", + "DMs":{ + "copyId":"Копировать ID ЛС", + "markRead":"Пометить как прочитанное", + "close":"Закрыть ЛС" + }, + "user":{ + "copyId":"Скопировать ID пользователя", + "online":"В сети", + "offline":"Не в сети", + "message":"Написать пользователю", + "block":"Заблокировать пользователя", + "unblock":"Разблокировать пользователя", + "friendReq":"Запрос дружбы", + "kick":"Выгнать участника", + "ban":"Забанить участника", + "addRole":"Добавить роли", + "removeRole":"Убрать роли" + }, + "login":{ + "checking":"Проверка инстанции", + "allGood":"Всё в порядке", + "invalid":"Неверная инстанция, попрбуйте снова", + "waiting":"Ожидание для проверки инстанции" + }, + "member":{ + "kick":"Выгнать $1 из $2", + "reason:":"Причина:", + "ban":"Забанить $1 из $2" + }, + "errorReconnect":"Не удалось подключиться к серверу, повтор попытки через $1 секунд...", + "retrying":"Повтор...", + "unableToConnect":"Не удалось подключиться к серверу Spacebar. Пожалуйста, попробуйте выйти и ещё раз войти." } From 79689b8d42b295f571242e8f497d2868033383e7 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 15 Nov 2024 15:06:30 -0600 Subject: [PATCH 0065/1330] add missing documentation --- translations/qqq.json | 1 + 1 file changed, 1 insertion(+) diff --git a/translations/qqq.json b/translations/qqq.json index 1ec1ac5d..ea080556 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -8,6 +8,7 @@ "comment":"Don't know how often I'll update this top part lol" }, "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty. readableName is needed for jank client to know to pick up a language in the settings.", + "readableName":"This should be the name of the language in the language, please do not translate english into the language for this name", "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", "permissions":{ "description":"This object contains the descriptions of permisions, along with their names." From f36b9382f12614a70cb6373bc229d3e7bcb5dbc8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 16 Nov 2024 15:12:12 -0600 Subject: [PATCH 0066/1330] emoji autofill --- src/webpage/emoji.ts | 96 ++++++++++++++++++++++++++++------------ src/webpage/jsontypes.ts | 1 + src/webpage/localuser.ts | 29 +++++++++--- src/webpage/markdown.ts | 3 +- src/webpage/style.css | 5 ++- 5 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 7380cd8e..03b1d429 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -1,18 +1,20 @@ import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; +import { emojijson } from "./jsontypes.js"; import{ Localuser }from"./localuser.js"; //I need to recompile the emoji format for translation class Emoji{ static emojis: { - name: string; - emojis: { - name: string; - emoji: string; - }[]; - }[]; + name: string; + emojis: { + name: string; + emoji: string; + }[]; + }[]; name: string; - id: string; + id?: string; + emoji?:string; animated: boolean; owner: Guild | Localuser; get guild(){ @@ -32,30 +34,34 @@ class Emoji{ return this.owner.info; } constructor( - json: { name: string; id: string; animated: boolean }, + json: emojijson, owner: Guild | Localuser ){ this.name = json.name; this.id = json.id; - this.animated = json.animated; + this.animated = json.animated||false; this.owner = owner; + this.emoji=json.emoji; } getHTML(bigemoji: boolean = false){ - const emojiElem = document.createElement("img"); - emojiElem.classList.add("md-emoji"); - emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); - emojiElem.crossOrigin = "anonymous"; - emojiElem.src = - this.info.cdn + - "/emojis/" + - this.id + - "." + - (this.animated ? "gif" : "png") + - "?size=32"; - - emojiElem.alt = this.name; - emojiElem.loading = "lazy"; - return emojiElem; + if(this.id){ + const emojiElem = document.createElement("img"); + emojiElem.classList.add("md-emoji"); + emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); + emojiElem.crossOrigin = "anonymous"; + emojiElem.src =this.info.cdn+"/emojis/"+this.id+"."+(this.animated ? "gif" : "png")+"?size=32"; + emojiElem.alt = this.name; + emojiElem.loading = "lazy"; + return emojiElem; + }else if(this.emoji){ + const emojiElem = document.createElement("span"); + emojiElem.classList.add("md-emoji"); + emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); + emojiElem.textContent=this.emoji; + return emojiElem; + }else{ + throw new Error("This path should *never* be gone down, this means a malformed emoji"); + } } static decodeEmojiList(buffer: ArrayBuffer){ const view = new DataView(buffer, 0); @@ -92,10 +98,10 @@ class Emoji{ for(; cats !== 0; cats--){ const name = readString16(); const emojis: { - name: string; - skin_tone_support: boolean; - emoji: string; - }[] = []; + name: string; + skin_tone_support: boolean; + emoji: string; + }[] = []; let emojinumber = read16(); for(; emojinumber !== 0; emojinumber--){ //console.log(emojis); @@ -248,6 +254,40 @@ class Emoji{ menu.append(body); return promise; } + static searchEmoji(search:string,localuser:Localuser,results=50):[Emoji,number][]{ + const ranked:[emojijson,number][]=[]; + function similar(json:emojijson){ + if(json.name.includes(search)){ + ranked.push([json,search.length/json.name.length]); + return true; + }else if(json.name.toLowerCase().includes(search.toLowerCase())){ + ranked.push([json,search.length/json.name.length/1.4]); + return true; + }else{ + return false; + } + } + for(const group of this.emojis){ + for(const emoji of group.emojis){ + similar(emoji) + } + } + const weakGuild=new WeakMap(); + for(const guild of localuser.guilds){ + if(guild.id!=="@me"&&guild.emojis.length!==0){ + for(const emoji of guild.emojis){ + if(similar(emoji)){ + weakGuild.set(emoji,guild); + }; + } + } + } + ranked.sort((a,b)=>b[1]-a[1]); + return ranked.splice(0,results).map(a=>{ + return [new Emoji(a[0],weakGuild.get(a[0])||localuser),a[1]]; + + }) + } } Emoji.grabEmoji(); export{ Emoji }; diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 39114cf2..d9a2d2cb 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -166,6 +166,7 @@ type emojijson = { name: string; id?: string; animated?: boolean; + emoji?:string }; type guildjson = { diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 9bb7a3f4..36f6cc88 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -5,14 +5,15 @@ import{ AVoice }from"./audio.js"; import{ User }from"./user.js"; import{ Dialog }from"./dialog.js"; import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js"; -import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,userjson,wsjson,}from"./jsontypes.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"; -import{ getTextNodeAtPosition, MarkDown, saveCaretPosition }from"./markdown.js"; +import{ getTextNodeAtPosition, MarkDown }from"./markdown.js"; import { Bot } from "./bot.js"; import { Role } from "./role.js"; import { VoiceFactory } from "./voice.js"; import { I18n, langmap } from "./i18n.js"; +import { Emoji } from "./emoji.js"; const wsCodesRetry = new Set([4000,4001,4002, 4003, 4005, 4007, 4008, 4009]); @@ -1738,7 +1739,7 @@ class Localuser{ this.typeMd.txt = raw.split(""); this.typeMd.boxupdate(typebox); } - MDSearchOptions(options:[string,string][],original:string){ + MDSearchOptions(options:[string,string,void|HTMLElement][],original:string){ const div=document.getElementById("searchOptions"); if(!div)return; div.innerHTML=""; @@ -1751,7 +1752,12 @@ class Localuser{ i++; const span=document.createElement("span"); htmloptions.push(span); - span.textContent=thing[0]; + if(thing[2]){ + console.log(thing); + span.append(thing[2]); + } + + span.append(thing[0]); span.onclick=(e)=>{ if(e){ @@ -1838,7 +1844,7 @@ class Localuser{ } } maybe.sort((a,b)=>b[0]-a[0]); - this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `]),orginal); + this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `,undefined]),orginal); } async getUser(id:string){ if(this.userMap.has(id)){ @@ -1857,7 +1863,7 @@ class Localuser{ } } members.sort((a,b)=>b[1]-a[1]); - this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `]),original); + this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `,undefined]),original); } MDFindMention(name:string,original:string){ if(this.ws&&this.lookingguild){ @@ -1901,6 +1907,13 @@ class Localuser{ }) } } + findEmoji(search:string,orginal:string){ + const emj=Emoji.searchEmoji(search,this,10); + const map=emj.map(([emoji]):[string,string,HTMLElement]=>{ + return [emoji.name,emoji.id?`<${emoji.animated?"a":""}:${emoji.name}:${emoji.id}`:emoji.emoji as string,emoji.getHTML()] + }) + this.MDSearchOptions(map,orginal); + } search(str:string,pre:boolean){ if(!pre){ const match=str.match(this.autofillregex); @@ -1916,7 +1929,9 @@ class Localuser{ break; case ":": if(search.length>=2){ - console.log("implement me"); + this.findEmoji(search,str) + }else{ + this.MDSearchOptions([],""); } break; } diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 1179d1ee..f512fea2 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -612,8 +612,7 @@ txt[j + 1] === undefined) appendcurrent(); i = j; const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); - const owner = - this.owner instanceof Channel ? this.owner.guild : this.owner; + const owner = this.owner instanceof Channel ? this.owner.guild : this.owner; if(!owner) continue; const emoji = new Emoji( { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, diff --git a/src/webpage/style.css b/src/webpage/style.css index 28d0feec..e18717fa 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -29,13 +29,16 @@ body { bottom:0; width: calc(100% - 32px); box-sizing: border-box; - span { + > span { transition: background .1s; margin-bottom:.025in; margin-top:.025in; padding:.075in .05in; border-radius:.03in; cursor:pointer; + > span, img{ + margin-right:.05in; + } } span.selected{ background:var(--button-bg); From db82358f5ba8a7c5bebf516dbf26abfe82cb851d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 16 Nov 2024 15:30:44 -0600 Subject: [PATCH 0067/1330] fix bug --- src/webpage/localuser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 36f6cc88..26861a9a 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1910,7 +1910,7 @@ class Localuser{ findEmoji(search:string,orginal:string){ const emj=Emoji.searchEmoji(search,this,10); const map=emj.map(([emoji]):[string,string,HTMLElement]=>{ - return [emoji.name,emoji.id?`<${emoji.animated?"a":""}:${emoji.name}:${emoji.id}`:emoji.emoji as string,emoji.getHTML()] + return [emoji.name,emoji.id?`<${emoji.animated?"a":""}:${emoji.name}:${emoji.id}>`:emoji.emoji as string,emoji.getHTML()] }) this.MDSearchOptions(map,orginal); } From 64672f4568dc2f78d308f7b63eb500aebecb4ca8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 16 Nov 2024 22:20:58 -0600 Subject: [PATCH 0068/1330] css fix --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index e18717fa..4fffd619 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2015,3 +2015,6 @@ fieldset input[type="radio"] { } } +.bigemoji{ + width:.6in; +} \ No newline at end of file From 074822e0089a23d5e57ba204b13697d13cf007de Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 18 Nov 2024 13:20:56 +0100 Subject: [PATCH 0069/1330] Localisation updates from https://translatewiki.net. --- translations/lt.json | 350 ++++++++++++++++++++ translations/qqq.json | 76 +---- translations/ru.json | 479 ++++++++++++++------------- translations/tr.json | 744 +++++++++++++++++++++--------------------- 4 files changed, 997 insertions(+), 652 deletions(-) create mode 100644 translations/lt.json diff --git a/translations/lt.json b/translations/lt.json new file mode 100644 index 00000000..2556df8b --- /dev/null +++ b/translations/lt.json @@ -0,0 +1,350 @@ +{ + "@metadata": { + "authors": [ + "Nokeoo" + ] + }, + "readableName": "Anglų", + "reply": "Atsakyti", + "copyrawtext": "Kopijuoti neapdorotą tekstą", + "copymessageid": "Kopijuoti žinutės id", + "permissions": { + "descriptions": { + "CREATE_INSTANT_INVITE": "Leidžia naudotojui kurti gildijos kvietimus", + "KICK_MEMBERS": "Leidžia naudotojui išmesti narius iš gildijos", + "BAN_MEMBERS": "Leidžia naudotojui blokuoti narius gildijoje", + "ADMINISTRATOR": "Leidžia visus leidimus ir apeina kanalo leidimų perrašymus. Tai pavojingas leidimas!", + "MANAGE_CHANNELS": "Leidžia naudotojui valdyti ir redaguoti kanalus", + "MANAGE_GUILD": "Leidžia valdyti ir redaguoti gildiją", + "ADD_REACTIONS": "Leidžia naudotojui pridėti reakcijas į žinutes", + "VIEW_AUDIT_LOG": "Leidžia naudotojui peržiūrėti audito žurnalą", + "PRIORITY_SPEAKER": "Leidžia naudoti prioritetinį garsiakalbį balso kanale", + "STREAM": "Leidžia naudotojui transliuoti", + "VIEW_CHANNEL": "Leidžia naudotojui peržiūrėti kanalą", + "SEND_MESSAGES": "Leidžia naudotojui siųsti pranešimus", + "SEND_TTS_MESSAGES": "Leidžia naudotojui siųsti teksto į kalbą pranešimus", + "MANAGE_MESSAGES": "Leidžia naudotojui ištrinti ne savo žinutes", + "EMBED_LINKS": "Leisti šio naudotojo atsiųstas nuorodas automatiškai įterpti", + "ATTACH_FILES": "Leidžia naudotojui pridėti failus", + "READ_MESSAGE_HISTORY": "Leidžia naudotojui skaityti žinučių istoriją", + "MENTION_EVERYONE": "Leidžia naudotojui paminėti visus", + "USE_EXTERNAL_EMOJIS": "Leidžia naudotojui naudoti išorinius jaustukus", + "VIEW_GUILD_INSIGHTS": "Leidžia naudotojui matyti gildijos įžvalgas", + "CONNECT": "Leidžia naudotojui prisijungti prie balso kanalo", + "SPEAK": "Leidžia naudotojui kalbėti balso kanalu", + "MUTE_MEMBERS": "Leidžia naudotojui nutildyti kitus narius", + "DEAFEN_MEMBERS": "Leidžia naudotojui apkurtinti kitus narius", + "MOVE_MEMBERS": "Leidžia naudotojui perkelti narius iš vieno balso kanalo į kitą", + "USE_VAD": "Leidžia naudotojams kalbėti balso kanale tiesiog kalbant", + "CHANGE_NICKNAME": "Leidžia naudotojui pakeisti savo slapyvardį", + "MANAGE_NICKNAMES": "Leidžia naudotojui keisti kitų narių slapyvardžius", + "MANAGE_ROLES": "Leidžia naudotojui redaguoti ir valdyti vaidmenis", + "MANAGE_GUILD_EXPRESSIONS": "Leidžia tvarkyti jaustukus, lipdukus ir garso lentas", + "USE_APPLICATION_COMMANDS": "Leidžia naudotojui naudoti programų komandas", + "REQUEST_TO_SPEAK": "Leidžia naudotojui pateikti užklausą kalbėti sceniniame kanale", + "MANAGE_EVENTS": "Leidžia naudotojui redaguoti ir valdyti renginius", + "MANAGE_THREADS": "Leidžia naudotojui ištrinti ir archyvuoti gijas bei peržiūrėti visas privačias gijas", + "CREATE_PUBLIC_THREADS": "Leidžia naudotojui kurti viešąsias gijas", + "CREATE_PRIVATE_THREADS": "Leidžia naudotojui kurti privačias gijas", + "USE_EXTERNAL_STICKERS": "Leidžia naudotojui naudoti išorinius lipdukus", + "SEND_MESSAGES_IN_THREADS": "Leidžia naudotojui siųsti pranešimus gijose", + "USE_EMBEDDED_ACTIVITIES": "Leidžia naudotojui naudoti įterptą veiklą", + "MODERATE_MEMBERS": "Leidžia naudotojui pristabdyti kitus naudotojus, kad jie negalėtų siųsti žinučių pokalbiuose ir gijuose, taip pat kalbėti balso ir scenos kanaluose", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Leidžia peržiūrėti vaidmenų prenumeratos įžvalgas", + "USE_SOUNDBOARD": "Leidžia naudoti garso pultą balso kanale", + "CREATE_GUILD_EXPRESSIONS": "Leidžia kurti jaustukus, lipdukus ir garso pulto garsus bei redaguoti ir ištrinti esamo naudotojo sukurtus.", + "CREATE_EVENTS": "Leidžia kurti suplanuotus įvykius ir redaguoti bei ištrinti dabartinio naudotojo sukurtus įvykius.", + "USE_EXTERNAL_SOUNDS": "Leidžia naudoti pasirinktinius garso pulto garsus iš kitų serverių", + "SEND_VOICE_MESSAGES": "Leidžia siųsti balso pranešimus", + "SEND_POLLS": "Leidžia siųsti apklausas", + "USE_EXTERNAL_APPS": "Leidžia naudotojo įdiegtoms programoms siųsti viešus atsakymus. Kai išjungta, naudotojams vis tiek bus leista naudoti savo programas, tačiau atsakymai bus trumpalaikiai. Tai taikoma tik programoms, kurios taip pat neįdiegtos serveryje." + }, + "readableNames": { + "CREATE_INSTANT_INVITE": "Sukurti kvietimą", + "KICK_MEMBERS": "Išmesti narius", + "BAN_MEMBERS": "Blokuoti narius", + "ADMINISTRATOR": "Administratorius", + "MANAGE_CHANNELS": "Tvarkyti kanalus", + "MANAGE_GUILD": "Tvarkyti gildiją", + "ADD_REACTIONS": "Pridėkite reakcijas", + "VIEW_AUDIT_LOG": "Žiūrėti audito žurnalą", + "PRIORITY_SPEAKER": "Pirmenybinis garsiakalbis", + "STREAM": "Vaizdo įrašas", + "VIEW_CHANNEL": "Žiūrėti kanalus", + "SEND_MESSAGES": "Siųsti žinutes", + "SEND_TTS_MESSAGES": "Siųsti teksto į kalbą žinutes", + "MANAGE_MESSAGES": "Tvarkyti žinutes", + "EMBED_LINKS": "Įterpti nuorodas", + "ATTACH_FILES": "Prisegti failus", + "READ_MESSAGE_HISTORY": "Skaityti žinučių istoriją", + "USE_EXTERNAL_EMOJIS": "Naudoti išorinius jaustukus", + "VIEW_GUILD_INSIGHTS": "Žiūrėti gildijos įžvalgas", + "CONNECT": "Prisijungti", + "SPEAK": "Kalbėti", + "MUTE_MEMBERS": "Nutildyti narius", + "DEAFEN_MEMBERS": "Kurtinti narius", + "MOVE_MEMBERS": "Perkelti narius", + "USE_VAD": "Naudoti balso aptikimą", + "CHANGE_NICKNAME": "Keisti slapyvardį", + "MANAGE_NICKNAMES": "Tvarkyti slapyvardžius", + "MANAGE_ROLES": "Valdykite vaidmenis", + "MANAGE_GUILD_EXPRESSIONS": "Valdyti išraiškas", + "USE_APPLICATION_COMMANDS": "Naudoti programų komandas", + "REQUEST_TO_SPEAK": "Prašymas kalbėti", + "MANAGE_EVENTS": "Tvarkyti įvykius", + "MANAGE_THREADS": "Tvarkyti gijas", + "CREATE_PUBLIC_THREADS": "Kurti viešas gijas", + "CREATE_PRIVATE_THREADS": "Kurti privačias gijas", + "USE_EXTERNAL_STICKERS": "Naudoti išorinius lipdukus", + "SEND_MESSAGES_IN_THREADS": "Siųsti žinutes gijose", + "USE_EMBEDDED_ACTIVITIES": "Naudoti veiklas", + "MODERATE_MEMBERS": "Pristabdyti narius", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Žiūrėti kūrėjų pajamų gavimo analizę", + "CREATE_GUILD_EXPRESSIONS": "Kurti išraiškas", + "CREATE_EVENTS": "Kurti įvykius", + "USE_EXTERNAL_SOUNDS": "Naudoti išorinius garsus", + "SEND_VOICE_MESSAGES": "Siųsti balso žinutes", + "SEND_POLLS": "Kurti apklausas", + "USE_EXTERNAL_APPS": "Naudoti išorines programas" + } + }, + "hideBlockedMessages": "Užblokavote šį naudotoją. Spustelėkite, kad paslėptumėte šias žinutes.", + "showBlockedMessages": "Užblokavote šį naudotoją. Spustelėkite, kad pamatytumėte užblokuotą $1 {{PLURAL:$1|žinutę|žinutes}}.", + "deleteConfirm": "Ar tikrai norite tai ištrinti?", + "yes": "Taip", + "no": "Ne", + "todayAt": "Šiandien $1", + "yesterdayAt": "Vakar $1", + "otherAt": "$1 $2", + "botSettings": "Roboto nustatymai", + "uploadPfp": "Įkelti pfp:", + "uploadBanner": "Įkelti banerį:", + "pronouns": "Įvardžiai:", + "bio": "Biografija:", + "profileColor": "Profilio spalva", + "botGuilds": "Gildijų botas yra:", + "leaveGuild": "Palikti gildiją", + "confirmGuildLeave": "Ar tikrai norite palikti $1", + "UrlGen": "URL generatorius", + "noMessages": "Atrodo, kad čia nėra pranešimų, būkite pirmas, kuris ką nors pasakykite!", + "blankMessage": "Tuščia žinutė", + "channel": { + "copyId": "Kopijuoti kanalo id", + "markRead": "Žymėti skaitytu", + "settings": "Nustatymai", + "delete": "Ištrinti kanalą", + "makeInvite": "Padaryti kvietimą", + "settingsFor": "$1 nustatymai", + "voice": "Balsas", + "text": "Tekstas", + "announcement": "Skelbimai", + "name:": "Pavadinimas:", + "topic:": "Tema:", + "nsfw:": "NSFW:", + "selectType": "Pasirinkite kanalo tipą", + "selectName": "Kanalo pavadinimas", + "selectCatName": "Kanalo pavadinimas", + "createChannel": "Sukurti kanalą", + "createCatagory": "Sukurti kategoriją" + }, + "switchAccounts": "Perjungti paskyras ⇌", + "accountNotStart": "Nepavyko paleisti paskyros", + "home": { + "uptimeStats": "Veikimo laikas: \n Visas laikas: $1%\nŠią savaitę: $2%\nŠiandien: $3%", + "warnOffiline": "Egzempliorius neprisijungęs, negali prisijungti" + }, + "htmlPages": { + "idpermissions": "Tai leis robotui:", + "addBot": "Pridėti prie serverio", + "loaddesc": "Tai neturėtų trukti ilgai", + "switchaccounts": "Perjungti paskyras", + "instanceField": "Egzempliorius:", + "emailField": "El. paštas:", + "pwField": "Slaptažodis:", + "loginButton": "Prisijungti", + "noAccount": "Neturite paskyros?", + "userField": "Naudotojo vardas:", + "pw2Field": "Įveskite slaptažodį dar kartą:", + "dobField": "Gimimo data:", + "createAccount": "Sukurti paskyrą", + "alreadyHave": "Jau turite paskyrą?", + "openClient": "Atidaryti klientą", + "box3description": "Visada vertiname pagalbą, nesvarbu, ar tai būtų pranešimai apie klaidas, kodas, ar tiesiog rašybos klaidų nurodymas." + }, + "register": { + "passwordError:": "Slaptažodis: $1", + "usernameError": "Naudotojo vardas: $1", + "emailError": "El. paštas: $1", + "DOBError": "Gimimo data: $1" + }, + "goingToURL": "Einate į $1. Tikrai norite ten eiti?", + "goThere": "Eiti ten", + "goThereTrust": "Eiti ten ir ateityje pasitikėti", + "nevermind": "Nesvarbu", + "submit": "siųsti", + "guild": { + "copyId": "Kopijuoti gildijos ID", + "markRead": "Žymėti skaitytu", + "notifications": "Pranešimai", + "leave": "Palikti gildiją", + "settings": "Nustatymai", + "delete": "Ištrinti gildiją", + "makeInvite": "Kurti kvietimą", + "settingsFor": "$1 nustatymai", + "name:": "Pavadinimas:", + "topic:": "Tema:", + "icon:": "Ikona:", + "overview": "Apžvalga", + "banner:": "Baneris:", + "region:": "Regionas:", + "roles": "Vaidmenys", + "selectnoti": "Pasirinkite pranešimų tipą", + "all": "visi", + "onlyMentions": "tik paminėjimai", + "none": "taškas", + "confirmLeave": "Ar tikrai norite išeiti?", + "yesLeave": "Taip, esu tikras", + "noLeave": "Nesvarbu", + "confirmDelete": "Ar tikrai norite ištrinti $1?", + "serverName": "Serverio pavadinimas:", + "yesDelete": "Taip, esu tikras", + "noDelete": "Nesvarbu", + "create": "Kurti gildiją", + "loadingDiscovery": "Įkeliama…", + "disoveryTitle": "Gildijos atradimas ($1) {{PLURAL:$1|įrašas|įrašai}}" + }, + "role": { + "displaySettings": "Rodinio nustatymai", + "name": "Vaidmens pavadinimas:", + "color": "Spalva", + "remove": "Pašalinti vaidmenį", + "delete": "Ištrinti vaidmenį", + "confirmDelete": "Ar tikrai norite ištrinti $1?" + }, + "settings": { + "unsaved": "Atsargiai, turite neišsaugotų pakeitimų", + "save": "Išsaugoti pakeitimus" + }, + "localuser": { + "settings": "Nustatymai", + "userSettings": "Naudotojo nustatymai", + "themesAndSounds": "Temos ir garsai", + "theme:": "Tema", + "notisound": "Pranešimo garsas:", + "accentColor": "Akcento spalva:", + "enableEVoice": "Įgalinti eksperimentinį balso palaikymą", + "VoiceWarning": "Ar tikrai norite tai įjungti, tai eksperimentinė versija ir gali sukelti problemų. (ši funkcija skirta kūrėjams, neįjunkite, jei nežinote, ką darote)", + "updateSettings": "Atnaujinti nustatymus", + "swSettings": "Paslaugos darbuotojo nustatymas", + "SWOff": "Išjungta", + "SWOffline": "Tik neprisijungus", + "SWOn": "Įjungta", + "clearCache": "Išvalyti talpyklą", + "CheckUpdate": "Tikrinti, ar yra naujinių", + "accountSettings": "Paskyros nustatymai", + "2faDisable": "Išjungti 2FA", + "badCode": "Netinkamas kodas", + "2faEnable": "Įjungti 2FA", + "2faCode:": "Kodas:", + "setUp2fa": "2FA sąranka", + "badPassword": "Neteisingas slaptažodis", + "changeEmail": "Keisti el. pašto adresą", + "password:": "Slaptažodis", + "newEmail:": "Naujas el. paštas", + "changeUsername": "Keisti naudotojo vardą", + "newUsername": "Naujas naudotojo vardas:", + "changePassword": "Keisti slaptažodį", + "oldPassword:": "Senas slaptažodis:", + "newPassword:": "Naujas slaptažodis:", + "PasswordsNoMatch": "Slaptažodžiai nesutampa", + "disableConnection": "Šis ryšys išjungtas serverio pusėje", + "devPortal": "Kūrėjo portalas", + "createApp": "Sukurti programą", + "team:": "Komanda:", + "appName": "Programos pavadinimas:", + "description": "Aprašymas:", + "privacyPolcyURL": "Privatumo politikos URL:", + "TOSURL": "Paslaugų teikimo sąlygų URL:", + "publicAvaliable": "Padaryti robotą viešai kviečiamu?", + "requireCode": "Reikalauti suteikti kodą norint pakviesti robotą?", + "manageBot": "Valdyti robotą", + "addBot": "Pridėti robotą", + "confirmAddBot": "Ar tikrai norite pridėti robotą prie šios programos? Nėra kelio atgal.", + "confuseNoBot": "Dėl kažkokių priežasčių ši programa neturi robotų (kol kas).", + "editingBot": "Redaguojamas robotas $1", + "botUsername": "Roboto naudotojo vardas:", + "botAvatar": "Roboto avataras:", + "advancedBot": "Išplėstiniai roboto nustatymai", + "botInviteCreate": "Roboto kvietimo kūrėjas", + "language": "Kalba:", + "connections": "Jungtys" + }, + "message": { + "reactionAdd": "Pridėti reakciją", + "delete": "Ištrinti žinutę", + "edit": "Redaguoti žinutę" + }, + "instanceStats": { + "name": "Egzemplioriaus statistika: $1", + "users": "Registruoti naudotojai: $1", + "servers": "Serveriai: $1", + "messages": "Žinutės: $1", + "members": "Nariai: $1" + }, + "inviteOptions": { + "title": "Pakvieskite žmones", + "30m": "30 minučių", + "1h": "1 valanda", + "6h": "6 valandos", + "12h": "12 valandų", + "1d": "1 diena", + "7d": "7 dienos", + "30d": "30 dienų", + "never": "Niekada", + "limit": "$1 {{PLURAL:$1|panaudojimas|panaudojimai}}", + "noLimit": "Nėra limito" + }, + "2faCode": "2FA kodas:", + "invite": { + "invitedBy": "Jus pakvietė $1", + "alreadyJoined": "Jau prisijungta", + "accept": "Priimti", + "noAccount": "Sukurkite paskyrą, kad priimtumėte kvietimą", + "longInvitedBy": "$1 pakvietė jus prisijungti prie $2", + "loginOrCreateAccount": "Prisijunkite arba susikurkite paskyrą ⇌", + "joinUsing": "Prisijunkite naudodami kvietimą", + "inviteLinkCode": "Pakvietimo nuoroda/kodas" + }, + "replyingTo": "Atsakoma $1", + "DMs": { + "copyId": "Kopijuoti DM id", + "markRead": "Žymėti skaitytu", + "close": "Uždaryti DM" + }, + "user": { + "copyId": "Kopijuoti naudotojo ID", + "online": "Prisijungę", + "offline": "Neprisijungę", + "message": "Parašyti naudotojui", + "block": "Blokuoti naudotoją", + "unblock": "atblokuoti naudotoją", + "friendReq": "Draugo prašymas", + "kick": "Išmesti narį", + "ban": "Blokuoti narį", + "addRole": "Pridėti vaidmenis", + "removeRole": "Pašalinti vaidmenis" + }, + "login": { + "checking": "Tikrinamas egzempliorius", + "allGood": "Viskas gerai", + "invalid": "Netinkamas egzempliorius, bandykite dar kartą", + "waiting": "Laukiama egzemplioriaus patikrinimo" + }, + "member": { + "kick": "Išmesti $1 iš $2", + "reason:": "Priežastis:", + "ban": "Blokuoti $1 iš $2" + }, + "errorReconnect": "Nepavyko prisijungti prie serverio, bandoma iš naujo po **$1** sekundžių...", + "retrying": "Bandoma dar kartą..." +} diff --git a/translations/qqq.json b/translations/qqq.json index ea080556..04e5e26c 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -1,65 +1,21 @@ { - "@metadata": { - "authors": [ - "MathMan05" - ], + "@metadata": { "last-updated": "2024/11/4", "locale": "en", - "comment":"Don't know how often I'll update this top part lol" + "comment": "Don't know how often I'll update this top part lol", + "authors": [ + "MathMan05" + ] + }, + "readableName": "This should be the name of the language in the language, please do not translate english into the language for this name", + "htmlPages": { + "box1Items": "this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" + }, + "register": { + "agreeTOS": "uses MarkDown" + }, + "localuser": { + "description": "This object contains strings related to the logged in user, which is mostly the settings for the user" }, - "How this works":"For the strings I'm using much of the same logic as https://github.com/wikimedia/jquery.i18n with some minor modifications, though I am not using jquery, some strings are are using MarkDown, the ones that are should hopefully be obvious. \nA quick summary of everything here, $number will be replaced by something after the fact, wether it be a name or number. You'll also see {{PLURAL:$1|message|messages}} which can support as many plural forms as you need, the first one $1 will be used to select the next ones, the next ones will be the number in order, so if $1 is one it'll select the first item, if it's 2 it'll select the second item, if $1 does not point to a valid item it'll select the last item. I've also implemented GENDER, though it's unused in the translations, it'd go {{GENDER:$1|male|female|neutral}}.\nThe json format is relatively simple, the base object has a @metadata that'll credit the authors of the translation, then it'll contain the translations. Untranslated strings should remain blank, Jank Client has code to fallback to other languages if strings don't exist in a translation, so do not leave the english translations in translation files that are not the english one, this is why the russian translation is largely empty. readableName is needed for jank client to know to pick up a language in the settings.", - "readableName":"This should be the name of the language in the language, please do not translate english into the language for this name", - "context":"Jank Client is a spacebar client, and due to spacebar being an implementation of the discord APIs, much of Jank Clients text is like that of discord.", - "permissions":{ - "description":"This object contains the descriptions of permisions, along with their names." - }, - "channel":{ - "description":"This object contains strings related to channels, which are analogous to discord channels." - }, - "home":{ - "description":"This contains the dynamic text for the homepage" - }, - "htmlPages":{ - "description":"This contains much of the text for the html pages so that they can have translations applied as well.", - "box1Items":"this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" - }, - "register":{ - "description":"This contains the dynamic text for the register page", - "agreeTOS":"uses MarkDown" - }, - "guild":{ - "description":"This object contains strings related to guilds, which are analogous to discord guilds." - }, - "role":{ - "description":"This object contains some strings related to roles, which are analogous to discord roles." - }, - "settings":{ - "description":"This object contains some strings related to settings menu which are fairly generic" - }, - "localuser":{ - "description":"This object contains strings related to the logged in user, which is mostly the settings for the user" - }, - "instanceStats":{ - "description":"This object contains strings related to instance stats" - }, - "inviteOptions":{ - "description":"This object contains strings for the invite creator" - }, - "invite":{ - "description":"This object contains strings for invites outside of their creation" - }, - "DMs":{ - "description":"This object contains strings related to dirrect messaging" - }, - "user":{ - "description":"This object contains strings related to users" - }, - "login":{ - "description":"This object contains strings related to the login page" - }, - "member":{ - "description":"This object contains strings related to guild members" - }, - "errorReconnect":"Uses MarkDown" - + "errorReconnect": "Uses MarkDown" } diff --git a/translations/ru.json b/translations/ru.json index bff4e7fe..21a2abdb 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -1,17 +1,19 @@ { "@metadata": { - "authors": [ - "StealthTheAngryBird" - ], "last-updated": "2024/15/24", "locale": "ru", - "comment":"Русский перевод Jank Client" + "comment": "Русский перевод Jank Client", + "authors": [ + "StealthTheAB", + "StealthTheAngryBird" + ] }, + "readableName": "Английский", "reply": "Ответить", - "copyrawtext":"Скопировать текст", - "copymessageid":"Копировать ID сообщения", - "permissions":{ - "descriptions":{ + "copyrawtext": "Скопировать текст", + "copymessageid": "Копировать ID сообщения", + "permissions": { + "descriptions": { "CREATE_INSTANT_INVITE": "Позволяет участникам приглашать новых участников в эту гильдию", "KICK_MEMBERS": "Позволяет участникам удалять других участников из этой гильдии", "BAN_MEMBERS": "Позволяет участникам банить других участников в этой гильдии", @@ -62,7 +64,7 @@ "SEND_POLLS": "Позволяет создавать голосования", "USE_EXTERNAL_APPS": "Позволяет установленным пользователем приложениям отправлять публичные ответы. Если отключено, пользователи по-прежнему смогут использовать свои приложения, но ответы будут эфемерными. Это применимо только к приложениям, которые не установлены на сервере." }, - "readableNames":{ + "readableNames": { "CREATE_INSTANT_INVITE": "Создание приглашения", "KICK_MEMBERS": "Выгонять участников", "BAN_MEMBERS": "Банить участников", @@ -114,226 +116,265 @@ "USE_EXTERNAL_APPS": "Использовать внешние приложения" } }, - "hideBlockedMessages":"Вы заблокировали этого пользователя, нажмите чтобы скрыть эти сообщения.", - "showBlockedMessages":"Вы заблокировали этого пользователя, нажмите чтобы просмотреть $1 $2 {{plural:$2|заблокированное|заблокированных}} {{PLURAL:$1|сообщение|сообщений}}.", - "deleteConfirm":"Вы точно уверены в том, что хотите удалить это?", - "yes":"Да", - "no":"Нет", - "todayAt":"Сегодня, в $1", - "yesterdayAt":"Вчера, в $1", - "otherAt":"$1 в $2", - "botSettings":"Настройки бота", - "uploadPfp":"Загрузить картинку профиля:", - "uploadBanner":"Загрузить баннер:", - "pronouns":"Местоимения:", - "bio":"Обо мне:", - "profileColor":"Цвет профиля", - "botGuilds":"Гильдии, в которых находится бот:", - "leaveGuild":"Покинуть гильдию", - "confirmGuildLeave":"Вы уверены, что хотите покинуть $1", - "UrlGen":"Генератор URL", - "typing":"$2 {{PLURAL:$1|печатают|печатает}}", - "noMessages":"Видимо здесь пока что нет сообщений, будьте первыми, кто напишет сюда!", - "blankMessage":"Пустое сообщение", - "channel":{ - "copyId":"Копировать ID канала", - "markRead":"Пометить как прочитанное", - "settings":"Настройки", - "delete":"Удалить канал", - "makeInvite":"Создать приглашение", - "settingsFor":"Настройки $1", - "voice":"Голос", - "text":"Текст", - "announcement":"Объявление", - "name:":"Название канала:", - "topic:":"Тема канала:", - "nsfw:":"Возрастное ограничение:", - "selectType":"Выберите тип канала", - "selectName":"Название канала", - "selectCatName":"Название канала", - "createChannel":"Создать канал", - "createCatagory":"Создать категорию" + "hideBlockedMessages": "Вы заблокировали этого пользователя, нажмите чтобы скрыть эти сообщения.", + "showBlockedMessages": "Вы заблокировали этого пользователя, нажмите, чтобы просмотреть $1 заблокированное/-ых {{PLURAL:$1| сообщение|сообщений}}.", + "deleteConfirm": "Вы точно уверены в том, что хотите удалить это?", + "yes": "Да", + "no": "Нет", + "todayAt": "Сегодня, в $1", + "yesterdayAt": "Вчера, в $1", + "otherAt": "$1 в $2", + "botSettings": "Настройки бота", + "uploadPfp": "Загрузить картинку профиля:", + "uploadBanner": "Загрузить баннер:", + "pronouns": "Местоимения:", + "bio": "Обо мне:", + "profileColor": "Цвет профиля", + "botGuilds": "Гильдии, в которых находится бот:", + "leaveGuild": "Покинуть гильдию", + "confirmGuildLeave": "Вы уверены, что хотите покинуть $1", + "UrlGen": "Генератор URL", + "typing": "$2 {{PLURAL:$1|печатают|печатает}}", + "noMessages": "Видимо здесь пока что нет сообщений, будьте первыми, кто напишет сюда!", + "blankMessage": "Пустое сообщение", + "channel": { + "copyId": "Копировать ID канала", + "markRead": "Пометить как прочитанное", + "settings": "Настройки", + "delete": "Удалить канал", + "makeInvite": "Создать приглашение", + "settingsFor": "Настройки $1", + "voice": "Голос", + "text": "Текст", + "announcement": "Объявление", + "name:": "Название канала:", + "topic:": "Тема канала:", + "nsfw:": "Возрастное ограничение:", + "selectType": "Выберите тип канала", + "selectName": "Название канала", + "selectCatName": "Название канала", + "createChannel": "Создать канал", + "createCatagory": "Создать категорию" }, - "switchAccounts":"Переключение между аккаунтами ⇌", - "accountNotStart":"Аккаунт не может быть запущен", - "home":{ - "uptimeStats":"Время онлайн: \n Всё время: $1\nЭта неделя: $2\nЭтот день: $3", - "warnOffiline":"Инстанция не в сети, невозможно подключиться" + "switchAccounts": "Переключение между аккаунтами ⇌", + "accountNotStart": "Аккаунт не может быть запущен", + "home": { + "uptimeStats": "Время онлайн: \n Всё время: $1\nЭта неделя: $2\nЭтот день: $3", + "warnOffiline": "Инстанция не в сети, невозможно подключиться" }, - "register":{ - "passwordError:":"Пароль: $1", - "usernameError":"Имя пользователя: $1", - "emailError":"Эл. почта: $1", - "DOBError":"Дата рождения: $1", - "agreeTOS":"Я соглашаюсь с [Условиями Использования]($1):", - "noTOS":"На этой инстанции нет Условий Использования, но всё равно примите их:" + "htmlPages": { + "idpermissions": "Это позволит боту:", + "addBot": "Добавить на сервер", + "loadingText": "Jank Client загружается", + "loaddesc": "Это не должно занять много времени", + "switchaccounts": "Переключение между учётными записями", + "instanceField": "Инстанция:", + "emailField": "Эл. почта:", + "pwField": "Пароль:", + "loginButton": "Войти", + "noAccount": "Нет учётной записи?", + "userField": "Имя пользователя:", + "pw2Field": "Введите пароль ещё раз:", + "dobField": "Дата рождения:", + "createAccount": "Создать аккаунт", + "alreadyHave": "Уже есть аккаунт?", + "openClient": "Открыть Клиент", + "welcomeJank": "Добро пожаловать в Jank Client", + "box1title": "Jank Client — это клиент, совместимый с Spacebar, стремящийся быть максимально хорошим и обладающий множеством функций, включая:", + "box1Items": "Личные сообщения|Поддержка реакций|Приглашения|Переключение аккаунтов|Настройки пользователя|Портал разработчиков|Приглашения ботов|Поддержка переводов", + "compatableInstances": "Инстанции, совместимые с Spacebar:", + "box3title": "Внести вклад в Jank Client", + "box3description": "Мы всегда ценим любую помощь, будь то в виде отчетов об ошибках, кода или даже просто указания на некоторые опечатки." }, - "leaving":"Вы покидаете Spacebar", - "goingToURL":"Вы переходите на сайт $1. Вы уверены в том, что хотите перейти туда?", - "goThere":"Перейти", - "goThereTrust":"Перейти и доверять в будущем", - "nevermind":"Не сейчас", - "submit":"подать", - "guild":{ - "copyId":"Копировать ID гильдии", - "markRead":"Пометить как прочитанное", - "notifications":"Уведомления", - "leave":"Покинуть гильдию", - "settings":"Настройки", - "delete":"Удалить гильдию", - "makeInvite":"Создать приглашение", - "settingsFor":"Настройки $1", - "name:":"Название:", - "topic:":"Тема:", - "icon:":"Иконка:", - "overview":"Обзор", - "banner:":"Баннер:", - "region:":"Регион:", - "roles":"Роли", - "selectnoti":"Выбрать тип уведомлений", - "all":"все", - "onlyMentions":"только упоминания", - "none":"никакие", - "confirmLeave":"Вы уверены, что хотите выйти?", - "yesLeave":"Да, я уверен", - "noLeave":"Не сейчас", - "confirmDelete":"Вы уверены, что хотите удалить $1?", - "serverName":"Название сервера:", - "yesDelete":"Да, я уверен", - "noDelete":"Не сейчас", - "create":"Создать гильдию", - "loadingDiscovery":"Загрузка...", - "disoveryTitle":"Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}" - }, - "role":{ - "displaySettings":"Настройки отображения", - "name":"Название роли:", - "hoisted":"Отображать раздельно:", - "mentionable":"Разрешить всем упоминать эту роль:", - "color":"Цвет", - "remove":"Убрать роль", - "delete":"Удалить роль", - "confirmDelete":"Вы уверены в том, что хотите удалить роль $1?" + "register": { + "passwordError:": "Пароль: $1", + "usernameError": "Имя пользователя: $1", + "emailError": "Эл. почта: $1", + "DOBError": "Дата рождения: $1", + "agreeTOS": "Я соглашаюсь с [Условиями Использования]($1):", + "noTOS": "На этой инстанции нет Условий Использования, но всё равно примите их:" + }, + "leaving": "Вы покидаете Spacebar", + "goingToURL": "Вы переходите на сайт $1. Вы уверены в том, что хотите перейти туда?", + "goThere": "Перейти", + "goThereTrust": "Перейти и доверять в будущем", + "nevermind": "Не сейчас", + "submit": "подать", + "guild": { + "copyId": "Копировать ID гильдии", + "markRead": "Пометить как прочитанное", + "notifications": "Уведомления", + "leave": "Покинуть гильдию", + "settings": "Настройки", + "delete": "Удалить гильдию", + "makeInvite": "Создать приглашение", + "settingsFor": "Настройки $1", + "name:": "Название:", + "topic:": "Тема:", + "icon:": "Иконка:", + "overview": "Обзор", + "banner:": "Баннер:", + "region:": "Регион:", + "roles": "Роли", + "selectnoti": "Выбрать тип уведомлений", + "all": "все", + "onlyMentions": "только упоминания", + "none": "никакие", + "confirmLeave": "Вы уверены, что хотите выйти?", + "yesLeave": "Да, я уверен", + "noLeave": "Не сейчас", + "confirmDelete": "Вы уверены, что хотите удалить $1?", + "serverName": "Название сервера:", + "yesDelete": "Да, я уверен", + "noDelete": "Не сейчас", + "create": "Создать гильдию", + "loadingDiscovery": "Загрузка...", + "disoveryTitle": "Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}" + }, + "role": { + "displaySettings": "Настройки отображения", + "name": "Название роли:", + "hoisted": "Отображать раздельно:", + "mentionable": "Разрешить всем упоминать эту роль:", + "color": "Цвет", + "remove": "Убрать роль", + "delete": "Удалить роль", + "confirmDelete": "Вы уверены в том, что хотите удалить роль $1?" + }, + "settings": { + "unsaved": "Осторожно, вы не сохранили изменения", + "save": "Сохранить изменения" + }, + "localuser": { + "settings": "Настройки", + "userSettings": "Настройки пользователя", + "themesAndSounds": "Темы и звуки", + "theme:": "Тема", + "notisound": "Звук уведомления:", + "accentColor": "Акцентный цвет:", + "enableEVoice": "Включить экспериментальную поддержку голосовых каналов", + "VoiceWarning": "Вы точно хотите включить это? Эта функция очень экспериментальная и может вызвать проблемы. (эта функция создана для разработчиков, пожалуйста не включайте если не знаете, что делаете)", + "updateSettings": "Обновить настройки", + "swSettings": "Service Worker setting", + "SWOff": "Выкл.", + "SWOffline": "Только вне сети", + "SWOn": "Вкл.", + "clearCache": "Очистить кеш", + "CheckUpdate": "Проверить наличие обновлений", + "accountSettings": "Настройки аккаунта", + "2faDisable": "Выключить двухфакторную аутентификацию", + "badCode": "Неверный код", + "2faEnable": "Включить двухфакторную аутентификацию", + "2faCode:": "Код:", + "setUp2fa": "Настройка двухфакторной аутентификации", + "badPassword": "Неверный пароль", + "setUp2faInstruction": "Скопируйте этот код в в приложение одноразового пароля по времени", + "2faCodeGive": "Вот ваш шестизначный код: $1. Он имеет ограничение в 30 секунд", + "changeDiscriminator": "Сменить дискриминатор", + "newDiscriminator": "Новый дискриминатор:", + "changeEmail": "Сменить адрес эл. почты", + "password:": "Пароль", + "newEmail:": "Новый адрес эл. почты", + "changeUsername": "Сменить имя пользователя", + "newUsername": "Новое имя пользователя:", + "changePassword": "Сменить пароль", + "oldPassword:": "Старый пароль:", + "newPassword:": "Новый пароль:", + "PasswordsNoMatch": "Пароли не совпадают", + "disableConnection": "Это подключение было отключено на стороне сервера", + "devPortal": "Портал разработчика", + "createApp": "Создать приложение", + "team:": "Команда:", + "appName": "Название приложения:", + "description": "Описание:", + "privacyPolcyURL": "URL Политики безопасности:", + "TOSURL": "URL Условий Использования:", + "publicAvaliable": "Сделать бота публично приглашаемым?", + "requireCode": "Требовать код для приглашения бота?", + "manageBot": "Управлять ботом", + "addBot": "Добавить бота", + "confirmAddBot": "Вы уверены в том, что хотите добавить бота в это приложение? Пути назад нет.", + "confuseNoBot": "По какой-то причине, у этого приложения нет бота (пока что).", + "editingBot": "Изменение бота $1", + "botUsername": "Название бота:", + "botAvatar": "Аватар бота:", + "resetToken": "Сбросить токен", + "confirmReset": "Вы уверены в том, что хотите сбросить токен бота? Ваш бот перестанет работать до того как вы его обновите.", + "tokenDisplay": "Токен: $1", + "saveToken": "Сохранить токен в localStorage", + "noToken": "Токен неизвестен, так что его нельзя сохранить localStorage, извините", + "advancedBot": "Расширенные настройки бота", + "botInviteCreate": "Создатель приглашения бота", + "language": "Язык:", + "connections": "Подключения" }, - "settings":{ - "unsaved":"Осторожно, вы не сохранили изменения", - "save":"Сохранить изменения" + "message": { + "reactionAdd": "Добавить реакцию", + "delete": "Удалить сообщение", + "edit": "Редактировать сообщение" }, - "localuser":{ - "settings":"Настройки", - "userSettings":"Настройки пользователя", - "themesAndSounds":"Темы и звуки", - "theme:":"Тема", - "notisound":"Звук уведомления:", - "accentColor":"Акцентный цвет:", - "enableEVoice":"Включить экспериментальную поддержку голосовых каналов", - "VoiceWarning":"Вы точно хотите включить это? Эта функция очень экспериментальная и может вызвать проблемы. (эта функция создана для разработчиков, пожалуйста не включайте если не знаете, что делаете)", - "updateSettings":"Обновить настройки", - "swSettings":"Service Worker setting", - "SWOff":"Выкл.", - "SWOffline":"Только вне сети", - "SWOn":"Вкл.", - "clearCache":"Очистить кеш", - "CheckUpdate":"Проверить наличие обновлений", - "accountSettings":"Настройки аккаунта", - "2faDisable":"Выключить двухфакторную аутентификацию", - "badCode":"Неверный код", - "2faEnable":"Включить двухфакторную аутентификацию", - "2faCode:":"Код:", - "setUp2fa":"Настройка двухфакторной аутентификации", - "badPassword":"Неверный пароль", - "setUp2faInstruction":"Скопируйте этот код в в приложение одноразового пароля по времени", - "2faCodeGive":"Вот ваш шестизначный код: $1. Он имеет ограничение в 30 секунд", - "changeDiscriminator":"Сменить дискриминатор", - "newDiscriminator":"Новый дискриминатор:", - "changeEmail":"Сменить адрес эл. почты", - "password:":"Пароль", - "newEmail:":"Новый адрес эл. почты", - "changeUsername":"Сменить имя пользователя", - "newUsername":"Новое имя пользователя:", - "changePassword":"Сменить пароль", - "oldPassword:":"Старый пароль:", - "newPassword:":"Новый пароль:", - "PasswordsNoMatch":"Пароли не совпадают", - "disableConnection":"Это подключение было отключено на стороне сервера", - "devPortal":"Портал разработчика", - "createApp":"Создать приложение", - "team:":"Команда:", - "appName":"Название приложения:", - "description":"Описание:", - "privacyPolcyURL":"URL Политики безопасности:", - "TOSURL":"URL Условий Использования:", - "publicAvaliable":"Сделать бота публично приглашаемым?", - "requireCode":"Требовать код для приглашения бота?", - "manageBot":"Управлять ботом", - "addBot":"Добавить бота", - "confirmAddBot":"Вы уверены в том, что хотите добавить бота в это приложение? Пути назад нет.", - "confuseNoBot":"По какой-то причине, у этого приложения нет бота (пока что).", - "editingBot":"Изменение бота $1", - "botUsername":"Название бота:", - "botAvatar":"Аватар бота:", - "resetToken":"Сбросить токен", - "confirmReset":"Вы уверены в том, что хотите сбросить токен бота? Ваш бот перестанет работать до того как вы его обновите.", - "tokenDisplay":"Токен: $1", - "saveToken":"Сохранить токен в localStorage", - "noToken":"Токен неизвестен, так что его нельзя сохранить localStorage, извините", - "advancedBot":"Расширенные настройки бота", - "botInviteCreate":"Создатель приглашения бота" + "instanceStats": { + "name": "Статистика инстанции: $1", + "users": "Зарегистрированные пользователи: $1", + "servers": "Сервера: $1", + "messages": "Сообщения: $1", + "members": "Участники: $1" }, - "inviteOptions":{ - "title":"Пригласить людей", - "30m":"30 минут", - "1h":"1 час", - "6h":"6 часов", - "12h":"12 часов", - "1d":"1 день", - "7d":"7 дней", - "30d":"30 дней", - "never":"Никогда", - "limit":"$1 {{PLURAL:$1|использование|использования|использований}}", - "noLimit":"Нет ограничений" + "inviteOptions": { + "title": "Пригласить людей", + "30m": "30 минут", + "1h": "1 час", + "6h": "6 часов", + "12h": "12 часов", + "1d": "1 день", + "7d": "7 дней", + "30d": "30 дней", + "never": "Никогда", + "limit": "$1 {{PLURAL:$1|использование|использования|использований}}", + "noLimit": "Нет ограничений" }, - "invite":{ - "invitedBy":"Вас пригласил пользователь $1", - "alreadyJoined":"Уже в гильдии", - "accept":"Принять", - "noAccount":"Создайте аккаунт, чтобы принять это приглашение", - "longInvitedBy":"Пользователь $1 пригласил вас на $2", - "loginOrCreateAccount":"Войдите или создайте аккаунт ⇌", - "joinUsing":"Присоединиться с помощью приглашения", - "inviteLinkCode":"Ссылка-приглашение/Код" + "2faCode": "Код двухфакторной аутентификации:", + "invite": { + "invitedBy": "Вас пригласил пользователь $1", + "alreadyJoined": "Уже в гильдии", + "accept": "Принять", + "noAccount": "Создайте аккаунт, чтобы принять это приглашение", + "longInvitedBy": "Пользователь $1 пригласил вас на $2", + "loginOrCreateAccount": "Войдите или создайте аккаунт ⇌", + "joinUsing": "Присоединиться с помощью приглашения", + "inviteLinkCode": "Ссылка-приглашение/Код" }, - "replyingTo":"В ответ $1", - "DMs":{ - "copyId":"Копировать ID ЛС", - "markRead":"Пометить как прочитанное", - "close":"Закрыть ЛС" + "replyingTo": "В ответ $1", + "DMs": { + "copyId": "Копировать ID ЛС", + "markRead": "Пометить как прочитанное", + "close": "Закрыть ЛС" }, - "user":{ - "copyId":"Скопировать ID пользователя", - "online":"В сети", - "offline":"Не в сети", - "message":"Написать пользователю", - "block":"Заблокировать пользователя", - "unblock":"Разблокировать пользователя", - "friendReq":"Запрос дружбы", - "kick":"Выгнать участника", - "ban":"Забанить участника", - "addRole":"Добавить роли", - "removeRole":"Убрать роли" + "user": { + "copyId": "Скопировать ID пользователя", + "online": "В сети", + "offline": "Не в сети", + "message": "Написать пользователю", + "block": "Заблокировать пользователя", + "unblock": "Разблокировать пользователя", + "friendReq": "Запрос дружбы", + "kick": "Выгнать участника", + "ban": "Забанить участника", + "addRole": "Добавить роли", + "removeRole": "Убрать роли" }, - "login":{ - "checking":"Проверка инстанции", - "allGood":"Всё в порядке", - "invalid":"Неверная инстанция, попрбуйте снова", - "waiting":"Ожидание для проверки инстанции" + "login": { + "checking": "Проверка инстанции", + "allGood": "Всё в порядке", + "invalid": "Неверная инстанция, попрбуйте снова", + "waiting": "Ожидание для проверки инстанции" }, - "member":{ - "kick":"Выгнать $1 из $2", - "reason:":"Причина:", - "ban":"Забанить $1 из $2" + "member": { + "kick": "Выгнать $1 из $2", + "reason:": "Причина:", + "ban": "Забанить $1 из $2" }, - "errorReconnect":"Не удалось подключиться к серверу, повтор попытки через $1 секунд...", - "retrying":"Повтор...", - "unableToConnect":"Не удалось подключиться к серверу Spacebar. Пожалуйста, попробуйте выйти и ещё раз войти." + "errorReconnect": "Не удалось подключиться к серверу, повтор попытки через $1 секунд...", + "retrying": "Повтор...", + "unableToConnect": "Не удалось подключиться к серверу Spacebar. Пожалуйста, попробуйте выйти и ещё раз войти." } diff --git a/translations/tr.json b/translations/tr.json index 6944425c..64e6d15f 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -1,375 +1,373 @@ { - "@metadata": { - "authors": [ - "Erkin Alp Güney" - ], - "last-updated": "2024/11/24", - "locale": "tr", - "comment": "Sizli bizli" - }, - "readableName":"Türkçe", - - "reply": "Yanıtla", - "copyrawtext": "Ham metni kopyala", - "copymessageid": "Mesaj kimliğini kopyala", - "permissions": { - "descriptions": { - "CREATE_INSTANT_INVITE": "Kullanıcının sunucu için davet oluşturmasına izin verir", - "KICK_MEMBERS": "Kullanıcının sunucudan üyeleri atmasına izin verir", - "BAN_MEMBERS": "Kullanıcının sunucudan üyeleri yasaklamasına izin verir", - "ADMINISTRATOR": "Tüm izinlere izin verir ve kanal izin geçersiz kılmalarını atlar. Bu tehlikeli bir izindir!", - "MANAGE_CHANNELS": "Kullanıcının kanalları yönetmesine ve düzenlemesine izin verir", - "MANAGE_GUILD": "Sunucunun yönetimine ve düzenlenmesine izin verir", - "ADD_REACTIONS": "Kullanıcının mesajlara tepki eklemesine izin verir", - "VIEW_AUDIT_LOG": "Kullanıcının denetim günlüğünü görüntülemesine izin verir", - "PRIORITY_SPEAKER": "Ses kanalında öncelikli konuşmacı kullanımına izin verir", - "STREAM": "Kullanıcının yayın yapmasına izin verir", - "VIEW_CHANNEL": "Kullanıcının kanalı görüntülemesine izin verir", - "SEND_MESSAGES": "Kullanıcının mesaj göndermesine izin verir", - "SEND_TTS_MESSAGES": "Kullanıcının metinden sese mesajlar göndermesine izin verir", - "MANAGE_MESSAGES": "Kullanıcının kendisine ait olmayan mesajları silmesine izin verir", - "EMBED_LINKS": "Bu kullanıcı tarafından gönderilen bağlantıların otomatik olarak gömülmesine izin verir", - "ATTACH_FILES": "Kullanıcının dosya eklemesine izin verir", - "READ_MESSAGE_HISTORY": "Kullanıcının mesaj geçmişini okumasına izin verir", - "MENTION_EVERYONE": "Kullanıcının herkesi etiketlemesine izin verir", - "USE_EXTERNAL_EMOJIS": "Kullanıcının harici emojileri kullanmasına izin verir", - "VIEW_GUILD_INSIGHTS": "Kullanıcının sunucu içgörülerini görmesine izin verir", - "CONNECT": "Kullanıcının bir ses kanalına bağlanmasına izin verir", - "SPEAK": "Kullanıcının bir ses kanalında konuşmasına izin verir", - "MUTE_MEMBERS": "Kullanıcının diğer üyeleri susturmasına izin verir", - "DEAFEN_MEMBERS": "Kullanıcının diğer üyeleri sağırlamasına izin verir", - "MOVE_MEMBERS": "Kullanıcının üyeleri ses kanalları arasında taşımasına izin verir", - "USE_VAD": "Kullanıcıların ses etkinliği ile konuşmasına izin verir", - "CHANGE_NICKNAME": "Kullanıcının kendi takma adını değiştirmesine izin verir", - "MANAGE_NICKNAMES": "Kullanıcının diğer üyelerin takma adlarını değiştirmesine izin verir", - "MANAGE_ROLES": "Kullanıcının rolleri düzenlemesine ve yönetmesine izin verir", - "MANAGE_WEBHOOKS": "Webhook'ların yönetimine ve düzenlenmesine izin verir", - "MANAGE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panolarını yönetmeye izin verir", - "USE_APPLICATION_COMMANDS": "Kullanıcının uygulama komutlarını kullanmasına izin verir", - "REQUEST_TO_SPEAK": "Kullanıcının sahne kanalında konuşma isteğinde bulunmasına izin verir", - "MANAGE_EVENTS": "Kullanıcının etkinlikleri düzenlemesine ve yönetmesine izin verir", - "MANAGE_THREADS": "Kullanıcının konuları silmesine ve arşivlemesine ve tüm özel konuları görüntülemesine izin verir", - "CREATE_PUBLIC_THREADS": "Kullanıcının genel konular oluşturmasına izin verir", - "CREATE_PRIVATE_THREADS": "Kullanıcının özel konular oluşturmasına izin verir", - "USE_EXTERNAL_STICKERS": "Kullanıcının harici çıkartmaları kullanmasına izin verir", - "SEND_MESSAGES_IN_THREADS": "Kullanıcının konularda mesaj göndermesine izin verir", - "USE_EMBEDDED_ACTIVITIES": "Kullanıcının gömülü etkinlikleri kullanmasına izin verir", - "MODERATE_MEMBERS": "Kullanıcının diğer kullanıcıları susturmasına izin verir", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Abonelik içgörülerini görüntülemeye izin verir", - "USE_SOUNDBOARD": "Ses panosunu bir ses kanalında kullanmaya izin verir", - "CREATE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panosu sesleri oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", - "CREATE_EVENTS": "Zamanlanmış etkinlikler oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", - "USE_EXTERNAL_SOUNDS": "Diğer sunuculardan özel ses panosu seslerini kullanmaya izin verir", - "SEND_VOICE_MESSAGES": "Sesli mesajlar göndermeye izin verir", - "SEND_POLLS": "Anketler göndermeye izin verir", - "USE_EXTERNAL_APPS": "Kullanıcı tarafından yüklenen uygulamaların herkese açık yanıtlar göndermesine izin verir. Devre dışı bırakıldığında, kullanıcılar yine de uygulamalarını kullanabilir ancak yanıtlar geçici olacaktır. Bu, sunucuya yüklenmemiş uygulamalar için geçerlidir." - }, - "readableNames": { - "CREATE_INSTANT_INVITE": "Davet oluştur", - "KICK_MEMBERS": "Üyeleri at", - "BAN_MEMBERS": "Üyeleri yasakla", - "ADMINISTRATOR": "Yönetici", - "MANAGE_CHANNELS": "Kanalları yönet", - "MANAGE_GUILD": "Sunucuyu yönet", - "ADD_REACTIONS": "Tepkiler ekle", - "VIEW_AUDIT_LOG": "Denetim günlüğünü görüntüle", - "PRIORITY_SPEAKER": "Öncelikli konuşmacı", - "STREAM": "Video", - "VIEW_CHANNEL": "Kanalları görüntüle", - "SEND_MESSAGES": "Mesaj gönder", - "SEND_TTS_MESSAGES": "Metinden sese mesaj gönder", - "MANAGE_MESSAGES": "Mesajları yönet", - "EMBED_LINKS": "Bağlantıları göm", - "ATTACH_FILES": "Dosyaları ekle", - "READ_MESSAGE_HISTORY": "Mesaj geçmişini oku", - "MENTION_EVERYONE": "@everyone, @here ve tüm rolleri etiketle", - "USE_EXTERNAL_EMOJIS": "Harici emojileri kullan", - "VIEW_GUILD_INSIGHTS": "Sunucu içgörülerini görüntüle", - "CONNECT": "Bağlan", - "SPEAK": "Konuş", - "MUTE_MEMBERS": "Üyeleri sustur", - "DEAFEN_MEMBERS": "Üyeleri sağırla", - "MOVE_MEMBERS": "Üyeleri taşı", - "USE_VAD": "Ses etkinliği algılamayı kullan", - "CHANGE_NICKNAME": "Takma adı değiştir", - "MANAGE_NICKNAMES": "Takma adları yönet", - "MANAGE_ROLES": "Rolleri yönet", - "MANAGE_WEBHOOKS": "Webhook'ları yönet", - "MANAGE_GUILD_EXPRESSIONS": "İfadeleri yönet", - "USE_APPLICATION_COMMANDS": "Uygulama komutlarını kullan", - "REQUEST_TO_SPEAK": "Konuşma isteğinde bulun", - "MANAGE_EVENTS": "Etkinlikleri yönet", - "MANAGE_THREADS": "Konuları yönet", - "CREATE_PUBLIC_THREADS": "Genel konular oluştur", - "CREATE_PRIVATE_THREADS": "Özel konular oluştur", - "USE_EXTERNAL_STICKERS": "Harici çıkartmaları kullan", - "SEND_MESSAGES_IN_THREADS": "Konularda mesaj gönder", - "USE_EMBEDDED_ACTIVITIES": "Etkinlikleri kullan", - "MODERATE_MEMBERS": "Üyeleri zaman aşımına uğrat", - "VIEW_CREATOR_MONETIZATION_ANALYTICS": "İçerik üretici gelir analitiğini görüntüle", - "USE_SOUNDBOARD": "Ses panosunu kullan", - "CREATE_GUILD_EXPRESSIONS": "İfadeler oluştur", - "CREATE_EVENTS": "Etkinlikler oluştur", - "USE_EXTERNAL_SOUNDS": "Harici sesleri kullan", - "SEND_VOICE_MESSAGES": "Sesli mesajlar gönder", - "SEND_POLLS": "Anketler oluştur", - "USE_EXTERNAL_APPS": "Harici uygulamaları kullan" - } - }, - "hideBlockedMessages": "Bu kullanıcıyı engellediniz, bu mesajları gizlemek için tıklayın.", - "showBlockedMessages": "Bu kullanıcıyı engellediniz, $1 engellenmiş mesajı görmek için tıklayın.", - "deleteConfirm": "Bunu silmek istediğinizden emin misiniz?", - "yes": "Evet", - "no": "Hayır", - "todayAt": "Bugün saat $1", - "yesterdayAt": "Dün saat $1", - "otherAt": "$1 tarihinde saat $2", - "botSettings": "Bot Ayarları", - "uploadPfp": "Profil resmi yükle:", - "uploadBanner": "Afiş yükle:", - "pronouns": "Zamirler:", - "bio": "Biyografi:", - "profileColor": "Profil rengi", - "botGuilds": "Botun bulunduğu sunucular:", - "leaveGuild": "Sunucudan ayrıl", - "confirmGuildLeave": "$1 sunucusundan ayrılmak istediğinizden emin misiniz", - "UrlGen": "URL oluşturucu", - "typing": "$2 {{PLURAL:$1|yazıyor|yazıyorlar}}", - "noMessages": "Burada mesaj görünmüyor, ilk siz bir şey söyleyin!", - "blankMessage": "Boş Mesaj", - "channel": { - "copyId": "Kanal kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "settings": "Ayarlar", - "delete": "Kanalı sil", - "makeInvite": "Davet oluştur", - "settingsFor": "$1 için ayarlar", - "voice": "Ses", - "text": "Metin", - "announcement": "Duyurular", - "name:": "Ad:", - "topic:": "Konu:", - "nsfw:": "NSFW:", - "selectType": "Kanal türünü seç", - "selectName": "Kanal adı", - "selectCatName": "Kategori adı", - "createChannel": "Kanal oluştur", - "createCatagory": "Kategori oluştur" - }, - "switchAccounts": "Hesapları Değiştir ⇌", - "accountNotStart": "Hesap başlatılamadı", - "home": { - "uptimeStats": "Çalışma Süresi: \n Tüm zamanlar: $1%\nBu hafta: $2%\nBugün: $3%", - "warnOffiline": "Örnek çevrimdışı, bağlanılamıyor" - }, - "htmlPages": { - "idpermissions": "Bu bot şunlara izin verecek:", - "addBot": "Sunucuya ekle", - "loadingText": "Jank Client yükleniyor", - "loaddesc": "Bu uzun sürmemeli", - "switchaccounts": "Hesapları Değiştir", - "instanceField": "Örnek:", - "emailField": "E-posta:", - "pwField": "Parola:", - "loginButton": "Giriş Yap", - "noAccount": "Hesabınız yok mu?", - "userField": "Kullanıcı adı:", - "pw2Field": "Parolayı tekrar girin:", - "dobField": "Doğum tarihi:", - "createAccount": "Hesap oluştur", - "alreadyHave": "Zaten bir hesabınız var mı?", - "openClient": "İstemciyi Aç", - "welcomeJank": "Jank Client'a Hoş Geldiniz", - "box1title": "Jank Client, birçok özellikle mümkün olduğunca iyi olmaya çalışan bir Spacebar uyumlu istemcidir, bunlar arasında:", - "box1Items": "Doğrudan Mesajlaşma|Tepkiler desteği|Davetler|Hesap değiştirme|Kullanıcı ayarları|Geliştirici portalı|Bot davetleri|Çeviri desteği", - "compatableInstances": "Spacebar Uyumlu Örnekler:", - "box3title": "Jank Client'a Katkıda Bulunun", - "box3description": "Her zaman yardımı takdir ederiz, ister hata raporları, ister kod şeklinde olsun, hatta sadece bazı yazım hatalarını işaret etseniz bile." - }, - "register": { - "passwordError:": "Parola: $1", - "usernameError": "Kullanıcı adı: $1", - "emailError": "E-posta: $1", - "DOBError": "Doğum Tarihi: $1", - "agreeTOS": "[Hizmet Şartlarını]($1) kabul ediyorum:", - "noTOS": "Bu örneğin Hizmet Şartları yok, yine de TOS'u kabul et:" - }, - "leaving": "Spacebar'dan ayrılıyorsunuz", - "goingToURL": "$1 adresine gidiyorsunuz. Oraya gitmek istediğinizden emin misiniz?", - "goThere": "Oraya git", - "goThereTrust": "Oraya git ve gelecekte güven", - "nevermind": "Boşver", - "submit": "Gönder", - "guild": { - "copyId": "Sunucu kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "notifications": "Bildirimler", - "leave": "Sunucudan ayrıl", - "settings": "Ayarlar", - "delete": "Sunucuyu sil", - "makeInvite": "Davet oluştur", - "settingsFor": "$1 için ayarlar", - "name:": "Ad:", - "topic:": "Konu:", - "icon:": "Simge:", - "overview": "Genel Bakış", - "banner:": "Afiş:", - "region:": "Bölge:", - "roles": "Roller", - "selectnoti": "Bildirim türünü seç", - "all": "hepsi", - "onlyMentions": "sadece bahsetmeler", - "none": "hiçbiri", - "confirmLeave": "Ayrılmak istediğinizden emin misiniz?", - "yesLeave": "Evet, eminim", - "noLeave": "Boşver", - "confirmDelete": "$1 sunucusunu silmek istediğinizden emin misiniz?", - "serverName": "Sunucu adı:", - "yesDelete": "Evet, eminim", - "noDelete": "Boşver", - "create": "Sunucu oluştur", - "loadingDiscovery": "Yükleniyor...", - "disoveryTitle": "Sunucu keşfi ($1) girdi" - }, - "role": { - "displaySettings": "Görüntü Ayarları", - "name": "Rol adı:", - "hoisted": "Listede göster:", - "mentionable": "Herkes bu rolü etiketleyebilir:", - "color": "Renk", - "remove": "Rolü kaldır", - "delete": "Rolü Sil", - "confirmDelete": "$1 silmek istediğinizden emin misiniz?" - }, - "settings": { - "unsaved": "Dikkatli olun, kaydedilmemiş değişiklikleriniz var", - "save": "Değişiklikleri kaydet" - }, - "localuser": { - "settings": "Ayarlar", - "userSettings": "Kullanıcı Ayarları", - "themesAndSounds": "Temalar & Sesler", - "theme:": "Tema", - "notisound": "Bildirim sesi:", - "accentColor": "Vurgu rengi:", - "enableEVoice": "Deneysel Ses desteğini etkinleştir", - "VoiceWarning": "Bunu etkinleştirmek istediğinizden emin misiniz, bu çok deneysel ve sorunlara neden olabilir. (bu özellik geliştiriciler içindir, ne yaptığınızı bilmiyorsanız lütfen etkinleştirmeyin)", - "updateSettings": "Ayarları Güncelle", - "swSettings": "Servis Çalışanı ayarı", - "SWOff": "Kapalı", - "SWOffline": "Yalnızca Çevrimdışı", - "SWOn": "Açık", - "clearCache": "Önbelleği temizle", - "CheckUpdate": "Güncellemeleri kontrol et", - "accountSettings": "Hesap Ayarları", - "2faDisable": "2FA'yı Devre Dışı Bırak", - "badCode": "Geçersiz kod", - "2faEnable": "2FA'yı Etkinleştir", - "2faCode:": "Kod:", - "setUp2fa": "2FA Kurulumu", - "badPassword": "Yanlış parola", - "setUp2faInstruction": "Bu gizli anahtarı TOTP uygulamanıza kopyalayın", - "2faCodeGive": "Gizli anahtarınız: $1 ve 6 haneli, 30 saniyelik bir token süresi var", - "changeDiscriminator": "Ayırıcıyı değiştir", - "newDiscriminator": "Yeni ayırıcı:", - "changeEmail": "E-postayı değiştir", - "password:": "Parola", - "newEmail:": "Yeni e-posta", - "changeUsername": "Kullanıcı adını değiştir", - "newUsername": "Yeni kullanıcı adı:", - "changePassword": "Parolayı değiştir", - "oldPassword:": "Eski parola:", - "newPassword:": "Yeni parola:", - "PasswordsNoMatch": "Parolalar eşleşmiyor", - "disableConnection": "Bu bağlantı sunucu tarafından devre dışı bırakıldı", - "devPortal": "Geliştirici Portalı", - "createApp": "Uygulama oluştur", - "team:": "Takım:", - "appName": "Uygulama adı:", - "description": "Açıklama:", - "privacyPolcyURL": "Gizlilik politikası URL'si:", - "TOSURL": "Hizmet Şartları URL'si:", - "publicAvaliable": "Botun herkese açık olarak davet edilmesine izin verilsin mi?", - "requireCode": "Botu davet etmek için kod izni gerekiyor mu?", - "manageBot": "Botu yönet", - "addBot": "Bot ekle", - "confirmAddBot": "Bu uygulamaya bir bot eklemek istediğinizden emin misiniz? Geri dönüşü yok.", - "confuseNoBot": "Nedense, bu uygulamanın henüz bir botu yok.", - "editingBot": "Bot $1 düzenleniyor", - "botUsername": "Bot kullanıcı adı:", - "botAvatar": "Bot avatarı:", - "resetToken": "Token'i sıfırla", - "confirmReset": "Bot token'ini sıfırlamak istediğinizden emin misiniz? Botunuz, güncelleyene kadar çalışmayı durduracak.", - "tokenDisplay": "Token: $1", - "saveToken": "Token'i localStorage'a kaydet", - "noToken": "Token'i bilmiyorum, bu yüzden localStorage'a kaydedemem, üzgünüm", - "advancedBot": "Gelişmiş Bot Ayarları", - "botInviteCreate": "Bot Davet Oluşturucu", - "language": "Dil:" - }, - "instanceStats": { - "name": "Örnek istatistikleri: $1", - "users": "Kayıtlı kullanıcılar: $1", - "servers": "Sunucular: $1", - "messages": "Mesajlar: $1", - "members": "Üyeler: $1" - }, - "inviteOptions": { - "title": "Kişileri Davet Et", - "30m": "30 Dakika", - "1h": "1 Saat", - "6h": "6 Saat", - "12h": "12 Saat", - "1d": "1 Gün", - "7d": "7 Gün", - "30d": "30 Gün", - "never": "Asla", - "limit": "$1 kullanım", - "noLimit": "Sınırsız" - }, - "2faCode": "2FA kodu:", - "invite": { - "invitedBy": "$1 sizi davet etti", - "alreadyJoined": "Zaten katıldınız", - "accept": "Kabul et", - "noAccount": "Daveti kabul etmek için bir hesap oluşturun", - "longInvitedBy": "$1 sizi $2'ya katılmaya davet etti", - "loginOrCreateAccount": "Giriş yapın veya hesap oluşturun ⇌", - "joinUsing": "Daveti kullanarak katıl", - "inviteLinkCode": "Davet Bağlantısı/Kodu" - }, - "replyingTo": "$1 yanıtlıyor", - "DMs": { - "copyId": "DM kimliğini kopyala", - "markRead": "Okundu olarak işaretle", - "close": "DM'yi kapat" - }, - "user": { - "copyId": "Kullanıcı kimliğini kopyala", - "online": "Çevrimiçi", - "offline": "Çevrimdışı", - "message": "Kullanıcıya mesaj gönder", - "block": "Kullanıcıyı engelle", - "unblock": "Engeli kaldır", - "friendReq": "Arkadaşlık isteği", - "kick": "Üyeyi at", - "ban": "Üyeyi yasakla", - "addRole": "Roller ekle", - "removeRole": "Roller kaldır" - }, - "login": { - "checking": "Örnek kontrol ediliyor", - "allGood": "Her şey yolunda", - "invalid": "Geçersiz örnek, tekrar deneyin", - "waiting": "Örneği kontrol etmek için bekleniyor" - }, - "member": { - "kick": "$1'ı $2'dan at", - "reason:": "Sebep:", - "ban": "$1'ı $2'dan yasakla" - }, - "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", - "retrying": "Yeniden deneniyor...", - "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." - + "@metadata": { + "last-updated": "2024/11/24", + "locale": "tr", + "comment": "Sizli bizli", + "authors": [ + "Erkin Alp Güney" + ] + }, + "readableName": "Türkçe", + "reply": "Yanıtla", + "copyrawtext": "Ham metni kopyala", + "copymessageid": "Mesaj kimliğini kopyala", + "permissions": { + "descriptions": { + "CREATE_INSTANT_INVITE": "Kullanıcının sunucu için davet oluşturmasına izin verir", + "KICK_MEMBERS": "Kullanıcının sunucudan üyeleri atmasına izin verir", + "BAN_MEMBERS": "Kullanıcının sunucudan üyeleri yasaklamasına izin verir", + "ADMINISTRATOR": "Tüm izinlere izin verir ve kanal izin geçersiz kılmalarını atlar. Bu tehlikeli bir izindir!", + "MANAGE_CHANNELS": "Kullanıcının kanalları yönetmesine ve düzenlemesine izin verir", + "MANAGE_GUILD": "Sunucunun yönetimine ve düzenlenmesine izin verir", + "ADD_REACTIONS": "Kullanıcının mesajlara tepki eklemesine izin verir", + "VIEW_AUDIT_LOG": "Kullanıcının denetim günlüğünü görüntülemesine izin verir", + "PRIORITY_SPEAKER": "Ses kanalında öncelikli konuşmacı kullanımına izin verir", + "STREAM": "Kullanıcının yayın yapmasına izin verir", + "VIEW_CHANNEL": "Kullanıcının kanalı görüntülemesine izin verir", + "SEND_MESSAGES": "Kullanıcının mesaj göndermesine izin verir", + "SEND_TTS_MESSAGES": "Kullanıcının metinden sese mesajlar göndermesine izin verir", + "MANAGE_MESSAGES": "Kullanıcının kendisine ait olmayan mesajları silmesine izin verir", + "EMBED_LINKS": "Bu kullanıcı tarafından gönderilen bağlantıların otomatik olarak gömülmesine izin verir", + "ATTACH_FILES": "Kullanıcının dosya eklemesine izin verir", + "READ_MESSAGE_HISTORY": "Kullanıcının mesaj geçmişini okumasına izin verir", + "MENTION_EVERYONE": "Kullanıcının herkesi etiketlemesine izin verir", + "USE_EXTERNAL_EMOJIS": "Kullanıcının harici emojileri kullanmasına izin verir", + "VIEW_GUILD_INSIGHTS": "Kullanıcının sunucu içgörülerini görmesine izin verir", + "CONNECT": "Kullanıcının bir ses kanalına bağlanmasına izin verir", + "SPEAK": "Kullanıcının bir ses kanalında konuşmasına izin verir", + "MUTE_MEMBERS": "Kullanıcının diğer üyeleri susturmasına izin verir", + "DEAFEN_MEMBERS": "Kullanıcının diğer üyeleri sağırlamasına izin verir", + "MOVE_MEMBERS": "Kullanıcının üyeleri ses kanalları arasında taşımasına izin verir", + "USE_VAD": "Kullanıcıların ses etkinliği ile konuşmasına izin verir", + "CHANGE_NICKNAME": "Kullanıcının kendi takma adını değiştirmesine izin verir", + "MANAGE_NICKNAMES": "Kullanıcının diğer üyelerin takma adlarını değiştirmesine izin verir", + "MANAGE_ROLES": "Kullanıcının rolleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_WEBHOOKS": "Webhook'ların yönetimine ve düzenlenmesine izin verir", + "MANAGE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panolarını yönetmeye izin verir", + "USE_APPLICATION_COMMANDS": "Kullanıcının uygulama komutlarını kullanmasına izin verir", + "REQUEST_TO_SPEAK": "Kullanıcının sahne kanalında konuşma isteğinde bulunmasına izin verir", + "MANAGE_EVENTS": "Kullanıcının etkinlikleri düzenlemesine ve yönetmesine izin verir", + "MANAGE_THREADS": "Kullanıcının konuları silmesine ve arşivlemesine ve tüm özel konuları görüntülemesine izin verir", + "CREATE_PUBLIC_THREADS": "Kullanıcının genel konular oluşturmasına izin verir", + "CREATE_PRIVATE_THREADS": "Kullanıcının özel konular oluşturmasına izin verir", + "USE_EXTERNAL_STICKERS": "Kullanıcının harici çıkartmaları kullanmasına izin verir", + "SEND_MESSAGES_IN_THREADS": "Kullanıcının konularda mesaj göndermesine izin verir", + "USE_EMBEDDED_ACTIVITIES": "Kullanıcının gömülü etkinlikleri kullanmasına izin verir", + "MODERATE_MEMBERS": "Kullanıcının diğer kullanıcıları susturmasına izin verir", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "Abonelik içgörülerini görüntülemeye izin verir", + "USE_SOUNDBOARD": "Ses panosunu bir ses kanalında kullanmaya izin verir", + "CREATE_GUILD_EXPRESSIONS": "Emoji, çıkartma ve ses panosu sesleri oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "CREATE_EVENTS": "Zamanlanmış etkinlikler oluşturmaya ve kullanıcının oluşturduklarını düzenlemeye ve silmeye izin verir", + "USE_EXTERNAL_SOUNDS": "Diğer sunuculardan özel ses panosu seslerini kullanmaya izin verir", + "SEND_VOICE_MESSAGES": "Sesli mesajlar göndermeye izin verir", + "SEND_POLLS": "Anketler göndermeye izin verir", + "USE_EXTERNAL_APPS": "Kullanıcı tarafından yüklenen uygulamaların herkese açık yanıtlar göndermesine izin verir. Devre dışı bırakıldığında, kullanıcılar yine de uygulamalarını kullanabilir ancak yanıtlar geçici olacaktır. Bu, sunucuya yüklenmemiş uygulamalar için geçerlidir." + }, + "readableNames": { + "CREATE_INSTANT_INVITE": "Davet oluştur", + "KICK_MEMBERS": "Üyeleri at", + "BAN_MEMBERS": "Üyeleri yasakla", + "ADMINISTRATOR": "Yönetici", + "MANAGE_CHANNELS": "Kanalları yönet", + "MANAGE_GUILD": "Sunucuyu yönet", + "ADD_REACTIONS": "Tepkiler ekle", + "VIEW_AUDIT_LOG": "Denetim günlüğünü görüntüle", + "PRIORITY_SPEAKER": "Öncelikli konuşmacı", + "STREAM": "Video", + "VIEW_CHANNEL": "Kanalları görüntüle", + "SEND_MESSAGES": "Mesaj gönder", + "SEND_TTS_MESSAGES": "Metinden sese mesaj gönder", + "MANAGE_MESSAGES": "Mesajları yönet", + "EMBED_LINKS": "Bağlantıları göm", + "ATTACH_FILES": "Dosyaları ekle", + "READ_MESSAGE_HISTORY": "Mesaj geçmişini oku", + "MENTION_EVERYONE": "@everyone, @here ve tüm rolleri etiketle", + "USE_EXTERNAL_EMOJIS": "Harici emojileri kullan", + "VIEW_GUILD_INSIGHTS": "Sunucu içgörülerini görüntüle", + "CONNECT": "Bağlan", + "SPEAK": "Konuş", + "MUTE_MEMBERS": "Üyeleri sustur", + "DEAFEN_MEMBERS": "Üyeleri sağırla", + "MOVE_MEMBERS": "Üyeleri taşı", + "USE_VAD": "Ses etkinliği algılamayı kullan", + "CHANGE_NICKNAME": "Takma adı değiştir", + "MANAGE_NICKNAMES": "Takma adları yönet", + "MANAGE_ROLES": "Rolleri yönet", + "MANAGE_WEBHOOKS": "Webhook'ları yönet", + "MANAGE_GUILD_EXPRESSIONS": "İfadeleri yönet", + "USE_APPLICATION_COMMANDS": "Uygulama komutlarını kullan", + "REQUEST_TO_SPEAK": "Konuşma isteğinde bulun", + "MANAGE_EVENTS": "Etkinlikleri yönet", + "MANAGE_THREADS": "Konuları yönet", + "CREATE_PUBLIC_THREADS": "Genel konular oluştur", + "CREATE_PRIVATE_THREADS": "Özel konular oluştur", + "USE_EXTERNAL_STICKERS": "Harici çıkartmaları kullan", + "SEND_MESSAGES_IN_THREADS": "Konularda mesaj gönder", + "USE_EMBEDDED_ACTIVITIES": "Etkinlikleri kullan", + "MODERATE_MEMBERS": "Üyeleri zaman aşımına uğrat", + "VIEW_CREATOR_MONETIZATION_ANALYTICS": "İçerik üretici gelir analitiğini görüntüle", + "USE_SOUNDBOARD": "Ses panosunu kullan", + "CREATE_GUILD_EXPRESSIONS": "İfadeler oluştur", + "CREATE_EVENTS": "Etkinlikler oluştur", + "USE_EXTERNAL_SOUNDS": "Harici sesleri kullan", + "SEND_VOICE_MESSAGES": "Sesli mesajlar gönder", + "SEND_POLLS": "Anketler oluştur", + "USE_EXTERNAL_APPS": "Harici uygulamaları kullan" + } + }, + "hideBlockedMessages": "Bu kullanıcıyı engellediniz, bu mesajları gizlemek için tıklayın.", + "showBlockedMessages": "Bu kullanıcıyı engellediniz, $1 engellenmiş mesajı görmek için tıklayın.", + "deleteConfirm": "Bunu silmek istediğinizden emin misiniz?", + "yes": "Evet", + "no": "Hayır", + "todayAt": "Bugün saat $1", + "yesterdayAt": "Dün saat $1", + "otherAt": "$1 tarihinde saat $2", + "botSettings": "Bot Ayarları", + "uploadPfp": "Profil resmi yükle:", + "uploadBanner": "Afiş yükle:", + "pronouns": "Zamirler:", + "bio": "Biyografi:", + "profileColor": "Profil rengi", + "botGuilds": "Botun bulunduğu sunucular:", + "leaveGuild": "Sunucudan ayrıl", + "confirmGuildLeave": "$1 sunucusundan ayrılmak istediğinizden emin misiniz", + "UrlGen": "URL oluşturucu", + "typing": "$2 {{PLURAL:$1|yazıyor|yazıyorlar}}", + "noMessages": "Burada mesaj görünmüyor, ilk siz bir şey söyleyin!", + "blankMessage": "Boş Mesaj", + "channel": { + "copyId": "Kanal kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "settings": "Ayarlar", + "delete": "Kanalı sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "voice": "Ses", + "text": "Metin", + "announcement": "Duyurular", + "name:": "Ad:", + "topic:": "Konu:", + "nsfw:": "NSFW:", + "selectType": "Kanal türünü seç", + "selectName": "Kanal adı", + "selectCatName": "Kategori adı", + "createChannel": "Kanal oluştur", + "createCatagory": "Kategori oluştur" + }, + "switchAccounts": "Hesapları Değiştir ⇌", + "accountNotStart": "Hesap başlatılamadı", + "home": { + "uptimeStats": "Çalışma Süresi: \n Tüm zamanlar: $1%\nBu hafta: $2%\nBugün: $3%", + "warnOffiline": "Örnek çevrimdışı, bağlanılamıyor" + }, + "htmlPages": { + "idpermissions": "Bu bot şunlara izin verecek:", + "addBot": "Sunucuya ekle", + "loadingText": "Jank Client yükleniyor", + "loaddesc": "Bu uzun sürmemeli", + "switchaccounts": "Hesapları Değiştir", + "instanceField": "Örnek:", + "emailField": "E-posta:", + "pwField": "Parola:", + "loginButton": "Giriş Yap", + "noAccount": "Hesabınız yok mu?", + "userField": "Kullanıcı adı:", + "pw2Field": "Parolayı tekrar girin:", + "dobField": "Doğum tarihi:", + "createAccount": "Hesap oluştur", + "alreadyHave": "Zaten bir hesabınız var mı?", + "openClient": "İstemciyi Aç", + "welcomeJank": "Jank Client'a Hoş Geldiniz", + "box1title": "Jank Client, birçok özellikle mümkün olduğunca iyi olmaya çalışan bir Spacebar uyumlu istemcidir, bunlar arasında:", + "box1Items": "Doğrudan Mesajlaşma|Tepkiler desteği|Davetler|Hesap değiştirme|Kullanıcı ayarları|Geliştirici portalı|Bot davetleri|Çeviri desteği", + "compatableInstances": "Spacebar Uyumlu Örnekler:", + "box3title": "Jank Client'a Katkıda Bulunun", + "box3description": "Her zaman yardımı takdir ederiz, ister hata raporları, ister kod şeklinde olsun, hatta sadece bazı yazım hatalarını işaret etseniz bile." + }, + "register": { + "passwordError:": "Parola: $1", + "usernameError": "Kullanıcı adı: $1", + "emailError": "E-posta: $1", + "DOBError": "Doğum Tarihi: $1", + "agreeTOS": "[Hizmet Şartlarını]($1) kabul ediyorum:", + "noTOS": "Bu örneğin Hizmet Şartları yok, yine de TOS'u kabul et:" + }, + "leaving": "Spacebar'dan ayrılıyorsunuz", + "goingToURL": "$1 adresine gidiyorsunuz. Oraya gitmek istediğinizden emin misiniz?", + "goThere": "Oraya git", + "goThereTrust": "Oraya git ve gelecekte güven", + "nevermind": "Boşver", + "submit": "Gönder", + "guild": { + "copyId": "Sunucu kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "notifications": "Bildirimler", + "leave": "Sunucudan ayrıl", + "settings": "Ayarlar", + "delete": "Sunucuyu sil", + "makeInvite": "Davet oluştur", + "settingsFor": "$1 için ayarlar", + "name:": "Ad:", + "topic:": "Konu:", + "icon:": "Simge:", + "overview": "Genel Bakış", + "banner:": "Afiş:", + "region:": "Bölge:", + "roles": "Roller", + "selectnoti": "Bildirim türünü seç", + "all": "hepsi", + "onlyMentions": "sadece bahsetmeler", + "none": "hiçbiri", + "confirmLeave": "Ayrılmak istediğinizden emin misiniz?", + "yesLeave": "Evet, eminim", + "noLeave": "Boşver", + "confirmDelete": "$1 sunucusunu silmek istediğinizden emin misiniz?", + "serverName": "Sunucu adı:", + "yesDelete": "Evet, eminim", + "noDelete": "Boşver", + "create": "Sunucu oluştur", + "loadingDiscovery": "Yükleniyor...", + "disoveryTitle": "Sunucu keşfi ($1) girdi" + }, + "role": { + "displaySettings": "Görüntü Ayarları", + "name": "Rol adı:", + "hoisted": "Listede göster:", + "mentionable": "Herkes bu rolü etiketleyebilir:", + "color": "Renk", + "remove": "Rolü kaldır", + "delete": "Rolü Sil", + "confirmDelete": "$1 silmek istediğinizden emin misiniz?" + }, + "settings": { + "unsaved": "Dikkatli olun, kaydedilmemiş değişiklikleriniz var", + "save": "Değişiklikleri kaydet" + }, + "localuser": { + "settings": "Ayarlar", + "userSettings": "Kullanıcı Ayarları", + "themesAndSounds": "Temalar & Sesler", + "theme:": "Tema", + "notisound": "Bildirim sesi:", + "accentColor": "Vurgu rengi:", + "enableEVoice": "Deneysel Ses desteğini etkinleştir", + "VoiceWarning": "Bunu etkinleştirmek istediğinizden emin misiniz, bu çok deneysel ve sorunlara neden olabilir. (bu özellik geliştiriciler içindir, ne yaptığınızı bilmiyorsanız lütfen etkinleştirmeyin)", + "updateSettings": "Ayarları Güncelle", + "swSettings": "Servis Çalışanı ayarı", + "SWOff": "Kapalı", + "SWOffline": "Yalnızca Çevrimdışı", + "SWOn": "Açık", + "clearCache": "Önbelleği temizle", + "CheckUpdate": "Güncellemeleri kontrol et", + "accountSettings": "Hesap Ayarları", + "2faDisable": "2FA'yı Devre Dışı Bırak", + "badCode": "Geçersiz kod", + "2faEnable": "2FA'yı Etkinleştir", + "2faCode:": "Kod:", + "setUp2fa": "2FA Kurulumu", + "badPassword": "Yanlış parola", + "setUp2faInstruction": "Bu gizli anahtarı TOTP uygulamanıza kopyalayın", + "2faCodeGive": "Gizli anahtarınız: $1 ve 6 haneli, 30 saniyelik bir token süresi var", + "changeDiscriminator": "Ayırıcıyı değiştir", + "newDiscriminator": "Yeni ayırıcı:", + "changeEmail": "E-postayı değiştir", + "password:": "Parola", + "newEmail:": "Yeni e-posta", + "changeUsername": "Kullanıcı adını değiştir", + "newUsername": "Yeni kullanıcı adı:", + "changePassword": "Parolayı değiştir", + "oldPassword:": "Eski parola:", + "newPassword:": "Yeni parola:", + "PasswordsNoMatch": "Parolalar eşleşmiyor", + "disableConnection": "Bu bağlantı sunucu tarafından devre dışı bırakıldı", + "devPortal": "Geliştirici Portalı", + "createApp": "Uygulama oluştur", + "team:": "Takım:", + "appName": "Uygulama adı:", + "description": "Açıklama:", + "privacyPolcyURL": "Gizlilik politikası URL'si:", + "TOSURL": "Hizmet Şartları URL'si:", + "publicAvaliable": "Botun herkese açık olarak davet edilmesine izin verilsin mi?", + "requireCode": "Botu davet etmek için kod izni gerekiyor mu?", + "manageBot": "Botu yönet", + "addBot": "Bot ekle", + "confirmAddBot": "Bu uygulamaya bir bot eklemek istediğinizden emin misiniz? Geri dönüşü yok.", + "confuseNoBot": "Nedense, bu uygulamanın henüz bir botu yok.", + "editingBot": "Bot $1 düzenleniyor", + "botUsername": "Bot kullanıcı adı:", + "botAvatar": "Bot avatarı:", + "resetToken": "Token'i sıfırla", + "confirmReset": "Bot token'ini sıfırlamak istediğinizden emin misiniz? Botunuz, güncelleyene kadar çalışmayı durduracak.", + "tokenDisplay": "Token: $1", + "saveToken": "Token'i localStorage'a kaydet", + "noToken": "Token'i bilmiyorum, bu yüzden localStorage'a kaydedemem, üzgünüm", + "advancedBot": "Gelişmiş Bot Ayarları", + "botInviteCreate": "Bot Davet Oluşturucu", + "language": "Dil:" + }, + "instanceStats": { + "name": "Örnek istatistikleri: $1", + "users": "Kayıtlı kullanıcılar: $1", + "servers": "Sunucular: $1", + "messages": "Mesajlar: $1", + "members": "Üyeler: $1" + }, + "inviteOptions": { + "title": "Kişileri Davet Et", + "30m": "30 Dakika", + "1h": "1 Saat", + "6h": "6 Saat", + "12h": "12 Saat", + "1d": "1 Gün", + "7d": "7 Gün", + "30d": "30 Gün", + "never": "Asla", + "limit": "$1 kullanım", + "noLimit": "Sınırsız" + }, + "2faCode": "2FA kodu:", + "invite": { + "invitedBy": "$1 sizi davet etti", + "alreadyJoined": "Zaten katıldınız", + "accept": "Kabul et", + "noAccount": "Daveti kabul etmek için bir hesap oluşturun", + "longInvitedBy": "$1 sizi $2'ya katılmaya davet etti", + "loginOrCreateAccount": "Giriş yapın veya hesap oluşturun ⇌", + "joinUsing": "Daveti kullanarak katıl", + "inviteLinkCode": "Davet Bağlantısı/Kodu" + }, + "replyingTo": "$1 yanıtlıyor", + "DMs": { + "copyId": "DM kimliğini kopyala", + "markRead": "Okundu olarak işaretle", + "close": "DM'yi kapat" + }, + "user": { + "copyId": "Kullanıcı kimliğini kopyala", + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "message": "Kullanıcıya mesaj gönder", + "block": "Kullanıcıyı engelle", + "unblock": "Engeli kaldır", + "friendReq": "Arkadaşlık isteği", + "kick": "Üyeyi at", + "ban": "Üyeyi yasakla", + "addRole": "Roller ekle", + "removeRole": "Roller kaldır" + }, + "login": { + "checking": "Örnek kontrol ediliyor", + "allGood": "Her şey yolunda", + "invalid": "Geçersiz örnek, tekrar deneyin", + "waiting": "Örneği kontrol etmek için bekleniyor" + }, + "member": { + "kick": "$1'ı $2'dan at", + "reason:": "Sebep:", + "ban": "$1'ı $2'dan yasakla" + }, + "errorReconnect": "Sunucuya bağlanılamadı, **$1** saniye içinde yeniden denenecek...", + "retrying": "Yeniden deneniyor...", + "unableToConnect": "Spacebar sunucusuna bağlanılamıyor. Lütfen çıkış yapıp tekrar deneyin." } From 17d032b9eaed710424b415b8477e0286a2eaf1ad Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 18 Nov 2024 11:42:30 -0600 Subject: [PATCH 0070/1330] add \ --- src/webpage/markdown.ts | 71 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index f512fea2..b074f1c3 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -130,6 +130,17 @@ class MarkDown{ i++; } } + if(txt[i] === "\\"){ + const chatset=new Set("\\`{}[]()<>*_#+-.!|".split("")); + if(chatset.has(txt[i+1])){ + if(keep){ + current.textContent += txt[i]; + } + current.textContent += txt[i+1]; + i++; + continue; + } + } if(txt[i] === "\n"){ if(!stdsize){ appendcurrent(); @@ -152,13 +163,7 @@ class MarkDown{ let find = 0; let j = i + count; let init = true; - for( - ; - txt[j] !== undefined && -(txt[j] !== "\n" || count === 3) && -find !== count; - j++ - ){ + for(;txt[j] !== undefined &&(txt[j] !== "\n" || count === 3) &&find !== count;j++){ if(txt[j] === "`"){ find++; }else{ @@ -540,29 +545,29 @@ txt[j + 1] === undefined) }); else if(!parts[3] || parts[3] === "f") time = - dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - }) + - " " + - dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); + dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + }) + + " " + + dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); else if(parts[3] === "F") time = - dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - weekday: "long", - }) + - " " + - dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); + dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + weekday: "long", + }) + + " " + + dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); else if(parts[3] === "t") time = dateInput.toLocaleString(void 0, { hour: "2-digit", @@ -575,10 +580,7 @@ txt[j + 1] === undefined) second: "2-digit", }); else if(parts[3] === "R") - time = - Math.round( - (Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60 - ) + " minutes ago"; + time =Math.round((Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60) + " minutes ago"; } const timeElem = document.createElement("span"); @@ -589,10 +591,7 @@ txt[j + 1] === undefined) } } - if( - txt[i] === "<" && - (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")&&this.owner) - ){ + if(txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")&&this.owner)){ let found = false; const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; let j = i + build.length; From a827ed102b09c010269eb12e1a69ccc82886f145 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 18 Nov 2024 12:31:58 -0600 Subject: [PATCH 0071/1330] fix markdown again --- src/webpage/localuser.ts | 6 +++++- src/webpage/markdown.ts | 37 +++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 26861a9a..cf183670 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1737,7 +1737,10 @@ class Localuser{ console.log(replacewith); console.log(original); this.typeMd.txt = raw.split(""); - this.typeMd.boxupdate(typebox); + const match=original.match(this.autofillregex); + if(match){ + this.typeMd.boxupdate(typebox,replacewith.length-match[0].length); + } } MDSearchOptions(options:[string,string,void|HTMLElement][],original:string){ const div=document.getElementById("searchOptions"); @@ -1915,6 +1918,7 @@ class Localuser{ this.MDSearchOptions(map,orginal); } search(str:string,pre:boolean){ + console.log(str); if(!pre){ const match=str.match(this.autofillregex); diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index b074f1c3..a6004327 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -713,8 +713,8 @@ txt[j + 1] === undefined) box.onkeyup(new KeyboardEvent("_")); }; } - boxupdate(box: HTMLElement){ - const restore = saveCaretPosition(box); + boxupdate(box: HTMLElement,offset=0){ + const restore = saveCaretPosition(box,offset); box.innerHTML = ""; box.append(this.makeHTML({ keep: true })); if(restore){ @@ -829,7 +829,7 @@ txt[j + 1] === undefined) //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div let text = ""; let formatted=false; -function saveCaretPosition(context: HTMLElement){ +function saveCaretPosition(context: HTMLElement,offset=0){ const selection = window.getSelection() as Selection; if(!selection)return; const range = selection.getRangeAt(0); @@ -902,7 +902,7 @@ function saveCaretPosition(context: HTMLElement){ build+=baseString; } text=build; - const len=build.length; + const len=build.length+offset; return function restore(){ if(!selection)return; const pos = getTextNodeAtPosition(context, len); @@ -933,29 +933,46 @@ function getTextNodeAtPosition(root: Node, index: number):{ position: -1, }; } + let lastElm:Node=root; for(const node of root.childNodes as unknown as Node[]){ + lastElm=node; let len:number if(node instanceof HTMLElement){ len=MarkDown.gatherBoxText(node).length; }else{ len=(node.textContent as string).length } - if(len Date: Mon, 18 Nov 2024 12:34:44 -0600 Subject: [PATCH 0072/1330] remove excesive logging --- src/webpage/localuser.ts | 1 - src/webpage/markdown.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index cf183670..364b6b56 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1918,7 +1918,6 @@ class Localuser{ this.MDSearchOptions(map,orginal); } search(str:string,pre:boolean){ - console.log(str); if(!pre){ const match=str.match(this.autofillregex); diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index a6004327..0baa143c 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -960,7 +960,6 @@ function getTextNodeAtPosition(root: Node, index: number):{ } if(lastElm){ const position=(lastElm.textContent as string).length; - console.log(lastElm,lastElm.childNodes,root,position); return{ node: lastElm, position From 7eaf755048596bcc40ac296346e8b4b08cbda4f0 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 21 Nov 2024 13:21:19 +0100 Subject: [PATCH 0073/1330] Localisation updates from https://translatewiki.net. --- translations/ko.json | 236 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 translations/ko.json diff --git a/translations/ko.json b/translations/ko.json new file mode 100644 index 00000000..69f889a9 --- /dev/null +++ b/translations/ko.json @@ -0,0 +1,236 @@ +{ + "@metadata": { + "authors": [ + "Ykhwong" + ] + }, + "readableName": "영어", + "reply": "답변", + "copymessageid": "메시지 ID 복사", + "permissions": { + "descriptions": { + "SEND_MESSAGES": "사용자가 메시지를 보낼 수 있도록 허용합니다", + "MENTION_EVERYONE": "사용자가 모든 사람을 언급할 수 있도록 허용합니다", + "MANAGE_ROLES": "사용자가 역할을 편집하고 관리할 수 있도록 허용합니다", + "MANAGE_EVENTS": "사용자가 이벤트를 편집하고 관리할 수 있도록 허용합니다" + }, + "readableNames": { + "CREATE_INSTANT_INVITE": "초대장 만들기", + "ADMINISTRATOR": "관리자", + "MANAGE_CHANNELS": "채널 관리", + "ADD_REACTIONS": "반응 추가", + "VIEW_AUDIT_LOG": "감사 로그 보기", + "STREAM": "비디오", + "VIEW_CHANNEL": "채널 보기", + "SEND_MESSAGES": "메시지 보내기", + "MANAGE_MESSAGES": "메시지 관리", + "EMBED_LINKS": "링크 삽입", + "ATTACH_FILES": "파일 첨부", + "READ_MESSAGE_HISTORY": "메시지 역사 읽기", + "USE_EXTERNAL_EMOJIS": "외부 이모티콘 사용", + "CONNECT": "연결", + "MUTE_MEMBERS": "회원 알림을 받지 않음", + "CHANGE_NICKNAME": "별명 바꾸기", + "MANAGE_NICKNAMES": "별명 관리", + "MANAGE_ROLES": "역할 관리", + "MANAGE_WEBHOOKS": "웹훅 관리", + "MANAGE_EVENTS": "이벤트 관리", + "MANAGE_THREADS": "스레드 관리", + "CREATE_PUBLIC_THREADS": "공개 스레드 만들기", + "CREATE_PRIVATE_THREADS": "비공개 스레드 만들기", + "USE_EXTERNAL_STICKERS": "외부 스티커 사용", + "USE_SOUNDBOARD": "사운드보드 사용", + "CREATE_EVENTS": "이벤트 만들기", + "USE_EXTERNAL_SOUNDS": "외부 사운드 사용", + "SEND_VOICE_MESSAGES": "음성 메시지 보내기", + "USE_EXTERNAL_APPS": "외부 앱 사용" + } + }, + "hideBlockedMessages": "이 사용자를 차단하였습니다. 이 메시지를 숨기려면 클릭하세요", + "deleteConfirm": "이것을 삭제하시겠습니까?", + "yes": "예", + "no": "아니요", + "todayAt": "오늘 $1", + "yesterdayAt": "어제 $1", + "botSettings": "봇 설정", + "confirmGuildLeave": "$1 장소를 떠나시겠습니까", + "UrlGen": "URL 생성기", + "typing": "$2님이 타이핑 중{{PLURAL:$1|입니다}}", + "blankMessage": "빈 메시지", + "channel": { + "copyId": "채널 ID 복사", + "markRead": "읽은 것으로 표시", + "settings": "설정", + "delete": "채널 삭제", + "makeInvite": "초대하기", + "settingsFor": "$1 설정", + "voice": "음성", + "text": "텍스트", + "announcement": "알림", + "name:": "이름:", + "topic:": "주제:", + "nsfw:": "NSFW:", + "selectType": "채널 유형 선택", + "selectName": "채널 이름", + "selectCatName": "채널 이름", + "createChannel": "채널 만들기", + "createCatagory": "분류 만들기" + }, + "switchAccounts": "계정 전환 ⇌", + "htmlPages": { + "idpermissions": "이를 통해 봇은 다음 작업을 수행할 수 있습니다.", + "addBot": "서버에 추가", + "loaddesc": "오래 걸리지 않습니다", + "switchaccounts": "계정 전환", + "instanceField": "인스턴스:", + "emailField": "이메일:", + "pwField": "비밀번호:", + "loginButton": "로그인", + "noAccount": "계정이 없나요?", + "userField": "사용자 이름:", + "dobField": "출생일:", + "createAccount": "계정 만들기", + "alreadyHave": "계정이 이미 있습니까?", + "openClient": "클라이언트 열기" + }, + "register": { + "passwordError:": "비밀번호: $1", + "usernameError": "사용자 이름: $1", + "emailError": "이메일: $1", + "DOBError": "출생일: $1" + }, + "submit": "제출", + "guild": { + "markRead": "읽은 것으로 표시", + "notifications": "알림", + "settings": "설정", + "makeInvite": "초대하기", + "settingsFor": "$1 설정", + "name:": "이름:", + "topic:": "주제:", + "icon:": "아이콘:", + "overview": "개요", + "banner:": "배너:", + "region:": "지역:", + "roles": "역할", + "all": "모두", + "none": "노드", + "confirmLeave": "떠나시겠습니까?", + "yesLeave": "네, 확실합니다", + "serverName": "서버 이름:", + "yesDelete": "네, 확실합니다", + "loadingDiscovery": "불러오는 중..." + }, + "role": { + "displaySettings": "표시 설정", + "name": "역할 이름:", + "color": "색", + "remove": "역할 제거", + "delete": "역할 삭제", + "confirmDelete": "'$1' 항목을 삭제하시겠습니까?" + }, + "settings": { + "save": "변경사항 저장" + }, + "localuser": { + "settings": "설정", + "userSettings": "사용자 설정", + "themesAndSounds": "테마 및 사운드", + "theme:": "테마", + "notisound": "알림음:", + "VoiceWarning": "이 기능을 활성화하시겠습니까? 이 기능은 매우 실험적이며 문제가 발생할 가능성이 높습니다. (이 기능은 개발자를 위한 것이므로 무엇을 하는지 모르는 경우 활성화하지 마십시오)", + "updateSettings": "설정 업데이트", + "swSettings": "서비스 워커 설정", + "SWOffline": "오프라인 전용", + "clearCache": "캐시 지우기", + "CheckUpdate": "업데이트 확인", + "accountSettings": "계정 설정", + "2faDisable": "2FA 비활성화", + "badCode": "유효하지 않은 코드입니다", + "2faEnable": "2FA 활성화", + "2faCode:": "코드:", + "setUp2fa": "2FA 구성", + "badPassword": "비밀번호가 잘못되었습니다", + "changeEmail": "이메일 주소 바꾸기", + "password:": "비밀번호", + "newEmail:": "새 이메일", + "changeUsername": "사용자 이름 변경", + "newUsername": "새 사용자 이름:", + "changePassword": "비밀번호 바꾸기", + "oldPassword:": "이전 비밀번호:", + "newPassword:": "새 비밀번호:", + "PasswordsNoMatch": "비밀번호가 일치하지 않습니다", + "devPortal": "개발자 포털", + "createApp": "애플리케이션 만들기", + "team:": "팀:", + "appName": "애플리케이션 이름:", + "description": "설명:", + "privacyPolcyURL": "개인정보보호정책 URL:", + "TOSURL": "이용 약관 URL:", + "publicAvaliable": "봇을 공개적으로 초대할 수 있게 할까요?", + "manageBot": "봇 관리", + "addBot": "봇 추가", + "botUsername": "봇 사용자 이름:", + "botAvatar": "봇 아바타:", + "resetToken": "토큰 초기화", + "tokenDisplay": "토큰: $1", + "advancedBot": "고급 봇 설정", + "language": "언어:", + "connections": "연결" + }, + "message": { + "reactionAdd": "반응 추가", + "delete": "메시지 삭제", + "edit": "메시지 편집" + }, + "instanceStats": { + "name": "인스턴스 통계: $1", + "users": "등록된 사용자: $1", + "servers": "서버: $1", + "messages": "메시지: $1" + }, + "inviteOptions": { + "title": "인원 초대", + "30m": "30분", + "1h": "1시간", + "6h": "6시간", + "12h": "12시간", + "1d": "1일", + "7d": "7일", + "30d": "30일", + "never": "없음", + "noLimit": "제한 없음" + }, + "2faCode": "2FA 코드:", + "invite": { + "invitedBy": "$1님이 당신을 초대했습니다", + "alreadyJoined": "이미 참여했습니다", + "accept": "수락", + "longInvitedBy": "$1님이 당신을 $2에 초대했습니다", + "inviteLinkCode": "초대 링크/코드" + }, + "DMs": { + "copyId": "DM ID 복사", + "markRead": "읽은 것으로 표시", + "close": "DM 닫기" + }, + "user": { + "copyId": "사용자 ID 복사", + "online": "온라인", + "offline": "오프라인", + "message": "사용자에게 메시지 보내기", + "block": "사용자 차단", + "unblock": "사용자 차단 해제", + "friendReq": "친구 요청", + "addRole": "역할 추가", + "removeRole": "역할 제거" + }, + "login": { + "checking": "인스턴스 확인 중", + "invalid": "유효하지 않은 인스턴스입니다. 다시 시도하세요" + }, + "member": { + "reason:": "이유:" + }, + "retrying": "다시 시도하는 중..." +} From 496cc8d50f30eb4d34f03231f57b21c08606f754 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 25 Nov 2024 13:20:41 +0100 Subject: [PATCH 0074/1330] Localisation updates from https://translatewiki.net. --- translations/de.json | 124 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 translations/de.json diff --git a/translations/de.json b/translations/de.json new file mode 100644 index 00000000..80aa4c34 --- /dev/null +++ b/translations/de.json @@ -0,0 +1,124 @@ +{ + "@metadata": { + "authors": [ + "Booky", + "Brettchenweber" + ] + }, + "readableName": "Deutsch", + "reply": "Antworten", + "copyrawtext": "Rohen Text kopieren", + "copymessageid": "Nachrichten-Id kopieren", + "permissions": { + "descriptions": { + "CREATE_INSTANT_INVITE": "Erlaubt dem Nutzer, Server-Einladungen zu erstellen", + "KICK_MEMBERS": "Erlaubt dem Nutzer, Mitglieder vom Server zu werfen", + "BAN_MEMBERS": "Erlaubt dem Nutzer, Mitglieder vom Server zu bannen", + "ADMINISTRATOR": "Erlaubt dem Nutzer alle Berechtigungen und umgeht das Überschreiben von Kanalberechtigungen. Dies ist eine gefährliche Berechtigung!", + "MANAGE_CHANNELS": "Erlaubt dem Nutzer, Kanäle zu verwalten und zu editieren", + "MANAGE_GUILD": "Erlaubt dem Nutzer, den Server zu verwalten und zu editieren", + "ADD_REACTIONS": "Erlaubt dem Nutzer, auf Nachrichten zu reagieren", + "VIEW_AUDIT_LOG": "Erlaubt dem Nutzer die Einsicht des Audit-Logs", + "PRIORITY_SPEAKER": "Erlaubt dem Nutzer, ein Prioritätssprecher in Sprachkanälen zu sein", + "STREAM": "Erlaubt dem Nutzer zu streamen", + "VIEW_CHANNEL": "Erlaubt dem Nutzer, den Kanal zu sehen", + "SEND_MESSAGES": "Erlaubt dem Nutzer, Nachrichten zu senden", + "SEND_TTS_MESSAGES": "Erlaubt dem Nutzer das Senden von Text-zu-Sprache Nachrichten", + "MANAGE_MESSAGES": "Erlaubt dem Nutzer das Löschen von Nachrichten anderer Nutzer", + "EMBED_LINKS": "Erlaubt dem Nutzer die automatische Einbettung von Links", + "ATTACH_FILES": "Erlaubt dem Nutzer den Anhang von Dateien", + "READ_MESSAGE_HISTORY": "Erlaubt dem Nutzer, den Nachrichtenverlauf einzusehen", + "MENTION_EVERYONE": "Erlaubt dem Nutzer die Erwähnung von @everyone", + "USE_EXTERNAL_EMOJIS": "Erlaubt dem Nutzer die Benutzung von externen Emojis", + "VIEW_GUILD_INSIGHTS": "Erlaubt dem Nutzer die Einsicht von Server Insights", + "CONNECT": "Erlaubt dem Nutzer die Verbindung zu Sprachkanälen", + "SPEAK": "Erlaubt dem Nutzer das Sprechen in Sprachkanälen", + "MUTE_MEMBERS": "Erlaubt dem Nutzer, andere Nutzer serverweit stummzuschalten", + "MOVE_MEMBERS": "Erlaubt dem Nutzer, andere Nutzer zwischen Sprachkanälen zu verschieben", + "USE_VAD": "Erlaubt dem Nutzer, in Sprachkanälen ohne Push-to-Talk zu sprechen", + "CHANGE_NICKNAME": "Erlaubt dem Nutzer, seinen eigenen Nicknamen zu ändern", + "MANAGE_NICKNAMES": "Erlaubt dem Nutzer, die Nicknamen von anderen Nutzern zu verwalten", + "MANAGE_ROLES": "Erlaubt dem Nutzer, die Serverrollen zu verwalten und zu editieren", + "MANAGE_WEBHOOKS": "Erlaubt dem Nutzer, Webhooks zu verwalten und zu editieren", + "MANAGE_GUILD_EXPRESSIONS": "Erlaubt dem Nutzer, Emojis, Sticker und Soundboard Sounds zu verwalten", + "USE_APPLICATION_COMMANDS": "Erlaubt dem Nutzer die Verwendung von Befehlen bestimmter Anwendungen", + "REQUEST_TO_SPEAK": "Erlaubt dem Nutzer, in einem Bühnenkanal das Rederecht zu erbitten", + "MANAGE_EVENTS": "Erlaubt dem Nutzer das Verwalten und Editieren von Events" + } + }, + "hideBlockedMessages": "Dieser Benutzer ist blockiert, klicke, um die Nachrichten zu verbergen.", + "showBlockedMessages": "Dieser Benutzer ist blockiert, klicke, um die {{PLURAL:$1| blockierte Nachricht|$1 blockierten Nachrichten}} anzuzeigen.", + "deleteConfirm": "Bist du dir sicher, dass das gelöscht werden soll?", + "yes": "Ja", + "no": "Nein", + "todayAt": "Heute um $1", + "yesterdayAt": "Gestern um $1", + "otherAt": "$1, $2", + "botSettings": "Boteinstellungen", + "uploadPfp": "Profilbild hochladen:", + "uploadBanner": "Banner hochladen:", + "pronouns": "Pronomen:", + "bio": "Bio:", + "profileColor": "Profilfarbe", + "leaveGuild": "Server verlassen", + "noMessages": "Es scheinen keinen Nachrichten vorhanden zu sein, sei der Erste, der etwas sagt!", + "blankMessage": "Leere Nachricht", + "channel": { + "copyId": "Kanal-ID kopieren", + "markRead": "Als gelesen markieren", + "settings": "Einstellungen", + "delete": "Kanal löschen", + "makeInvite": "Einladung erstellen", + "settingsFor": "Einstellungen für $1", + "voice": "Sprache", + "text": "Text", + "announcement": "Ankündigungen" + }, + "switchAccounts": "Konto wechseln ⇌", + "home": { + "uptimeStats": "Uptime: \n Insgesamt: $1%\nDiese Woche: $2%\nHeute: $3%", + "warnOffiline": "Instanz ist offline, Verbindung kann nicht hergestellt werden" + }, + "htmlPages": { + "loadingText": "Jank Client wird geladen", + "loaddesc": "Das sollte nicht lange dauern", + "openClient": "Client öffnen", + "welcomeJank": "Willkommen bei Jank Client", + "box1title": "Jank Client ist ein Spacebar-kompatibler Client, welcher versucht so gut wie möglich sein durch einige Funktionen wie:", + "box1Items": "Direktnachrichten|Reaktionen auf Nachrichten|Einladungen|Kontowechsel|Benutzereinstellungen|Entwicklerportal|Bot-Einladungen|Übersetzungen", + "compatableInstances": "Einige Spacebar-Instanzen:", + "box3title": "Unterstützung in der Entwicklung von Jank Client", + "box3description": "Wir freuen uns immer über Hilfe, sei es in Form von Fehlerberichten, Programmierung oder einfach nur das Hinweisen auf Tippfehler." + }, + "register": { + "passwordError:": "Passwort: $1", + "usernameError": "Benutzername: $1", + "emailError": "E-Mail-Adresse: $1", + "DOBError": "Geburtsdatum: $1", + "agreeTOS": "Ich stimme den [Nutzungsbedingungen]($1) zu:", + "noTOS": "Diese Instanz hat keine Nutzungsbedingungen, akzeptiere die ToS trotzdem:" + }, + "leaving": "Du verlässt Spacebar", + "goingToURL": "Du wirst zu $1 gehen. Bist du sicher, dass du dorthin gehen willst?", + "goThere": "Ja", + "goThereTrust": "Ja, vertraue auch in der Zukunft", + "nevermind": "Nein", + "guild": { + "confirmLeave": "Bist du dir sicher, dass du den Server verlassen möchtest?", + "yesLeave": "Ja, ich bin mir sicher", + "noLeave": "Doch nicht", + "serverName": "Name des Servers:", + "yesDelete": "Ja, ich bin mir sicher", + "noDelete": "Doch nicht", + "loadingDiscovery": "Lade..." + }, + "role": { + "color": "Farbe", + "remove": "Rolle entfernen", + "delete": "Rolle löschen" + }, + "localuser": { + "privacyPolcyURL": "Datenschutzerklärung:", + "TOSURL": "Nutzungsbedingungen:" + } +} From 343de1e74fc614e3f624407a44d2f280f903c032 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 13:11:44 -0600 Subject: [PATCH 0075/1330] various changes+hover events --- .gitignore | 3 ++- package.json | 16 +++++++++------ src/webpage/hover.ts | 43 ++++++++++++++++++++++++++++++++++++++++ src/webpage/localuser.ts | 1 + src/webpage/message.ts | 13 +++++++++++- src/webpage/style.css | 12 +++++++++-- 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 src/webpage/hover.ts diff --git a/.gitignore b/.gitignore index e9f9a829..84887f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -135,4 +135,5 @@ CC uptime.json .directory .dist/ -bun.lockb \ No newline at end of file +bun.lockb +src/webpage/translations/langs.js diff --git a/package.json b/package.json index b8fd3347..47f8481e 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,7 @@ "author": "MathMan05", "license": "GPL-3.0", "dependencies": { - "@html-eslint/parser": "^0.27.0", - "@stylistic/eslint-plugin-js": "^2.8.0", - "@swc/core": "^1.7.26", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", "compression": "^1.7.4", - "eslint-plugin-html": "^8.1.1", "express": "^4.19.2", "gulp-sourcemaps": "^3.0.0", "gulp-swc": "^2.2.0", @@ -26,6 +20,16 @@ "ts-to-jsdoc": "^2.2.0" }, "devDependencies": { + "@html-eslint/parser": "^0.27.0", + "eslint-plugin-html": "^8.1.1", + "@stylistic/eslint-plugin-js": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", + "@rsbuild/core": "^1.1.4", + "@rsbuild/plugin-node-polyfill": "^1.2.0", + "@swc/core": "^1.7.26", + "rspack": "^0.1.1", + "swc": "^1.0.11", "@eslint/js": "^9.10.0", "@html-eslint/eslint-plugin": "^0.25.0", "@stylistic/eslint-plugin": "^2.3.0", diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts new file mode 100644 index 00000000..fcd5982a --- /dev/null +++ b/src/webpage/hover.ts @@ -0,0 +1,43 @@ +import { Contextmenu } from "./contextmenu.js"; +import { MarkDown } from "./markdown.js"; + +class Hover{ + str:string|MarkDown + constructor(txt:string|MarkDown){ + this.str=txt; + } + addEvent(elm:HTMLElement){ + let timeOut=setTimeout(()=>{},0); + let elm2=document.createElement("div"); + elm.addEventListener("mouseover",()=>{ + timeOut=setTimeout(()=>{ + elm2=this.makeHover(elm); + },750) + }); + elm.addEventListener("mouseout",()=>{ + clearTimeout(timeOut); + elm2.remove(); + }) + } + makeHover(elm:HTMLElement){ + const div=document.createElement("div"); + if(this.str instanceof MarkDown){ + div.append(this.str.makeHTML({stdsize:true})) + }else{ + div.append(this.str); + } + const box=elm.getBoundingClientRect(); + div.style.top=(box.bottom+4)+"px"; + div.style.left=Math.floor(box.left+box.width/2)+"px"; + div.classList.add("hoverthing"); + div.style.opacity="0"; + setTimeout(()=>{ + div.style.opacity="1"; + },10) + document.body.append(div); + Contextmenu.keepOnScreen(div); + console.log(div,elm); + return div; + } +} +export{Hover} diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 364b6b56..489acc12 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1724,6 +1724,7 @@ class Localuser{ const typebox = document.getElementById("typebox") as CustomHTMLDivElement; this.typeMd=typebox.markdown; + this.typeMd.owner=this; this.typeMd.onUpdate=this.search.bind(this); } MDReplace(replacewith:string,original:string){ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index ea07e9b3..06f10c37 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -13,6 +13,7 @@ import{ Emoji }from"./emoji.js"; import{ Dialog }from"./dialog.js"; import{ mobile }from"./login.js"; import { I18n } from "./i18n.js"; +import { Hover } from "./hover.js"; class Message extends SnowFlake{ static contextmenu = new Contextmenu("message menu"); @@ -133,6 +134,7 @@ class Message extends SnowFlake{ } ); } + edited_timestamp:string|null=null; giveData(messagejson: messagejson){ const func = this.channel.infinite.snapBottom(); for(const thing of Object.keys(messagejson)){ @@ -510,7 +512,16 @@ class Message extends SnowFlake{ time.textContent = " " + formatTime(new Date(this.timestamp)); time.classList.add("timestamp"); userwrap.appendChild(time); - + const hover=new Hover(new Date(this.timestamp).toString()); + hover.addEvent(time); + if(this.edited_timestamp){ + const edit=document.createElement("span"); + edit.classList.add("timestamp"); + edit.textContent="(edited)"; + const hover=new Hover(new Date(this.edited_timestamp).toString()); + hover.addEvent(edit); + userwrap.append(edit); + } text.appendChild(userwrap); }else{ div.classList.remove("topMessage"); diff --git a/src/webpage/style.css b/src/webpage/style.css index 4fffd619..46fcff03 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -259,7 +259,15 @@ textarea { width: 12px; pointer-events: none; } - +.hoverthing{ + position:absolute; + background:var(--dock-bg); + padding:.04in; + border-radius:.05in; + transform: translate(-50%, 0); + transition: opacity .2s; + border: solid .03in var(--black); +} /* Animations */ @keyframes fade { 0%, 100% { @@ -2017,4 +2025,4 @@ fieldset input[type="radio"] { } .bigemoji{ width:.6in; -} \ No newline at end of file +} From ea48fe89d553b78197a997e782820fa17f700bee Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 13:13:17 -0600 Subject: [PATCH 0076/1330] fix transltion strings --- src/webpage/message.ts | 2 +- translations/en.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 06f10c37..2296c7a6 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -517,7 +517,7 @@ class Message extends SnowFlake{ if(this.edited_timestamp){ const edit=document.createElement("span"); edit.classList.add("timestamp"); - edit.textContent="(edited)"; + edit.textContent=I18n.getTranslation("message.edited"); const hover=new Hover(new Date(this.edited_timestamp).toString()); hover.addEvent(edit); userwrap.append(edit); diff --git a/translations/en.json b/translations/en.json index fb9123cc..781c4750 100644 --- a/translations/en.json +++ b/translations/en.json @@ -311,7 +311,8 @@ "message":{ "reactionAdd":"Add reaction", "delete":"Delete message", - "edit":"Edit message" + "edit":"Edit message", + "edited":"(edited)" }, "instanceStats":{ "name":"Instance stats: $1", From ce538b3909cf1c332832bf34eb1b654e9a8c2e4f Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 14:39:38 -0600 Subject: [PATCH 0077/1330] way improved editing of messages --- src/webpage/channel.ts | 10 +++ src/webpage/direct.ts | 1 + src/webpage/index.html | 2 +- src/webpage/index.ts | 44 ++++++------ src/webpage/localuser.ts | 65 +++++++++--------- src/webpage/markdown.ts | 142 +++++++++++++++++++++------------------ src/webpage/message.ts | 69 ++++++++++++++----- src/webpage/style.css | 16 +++-- 8 files changed, 204 insertions(+), 145 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 4ba0d356..95b6f05c 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -791,6 +791,15 @@ class Channel extends SnowFlake{ return new Message(json[0], this); } } + editLast(){ + let message:Message|undefined=this.lastmessage; + while(message&&message.author!==this.localuser.user){ + message=this.messages.get(this.idToPrev.get(message.id) as string); + } + if(message){ + message.setEdit(); + } + } static genid: number = 0; async getHTML(){ const id = ++Channel.genid; @@ -842,6 +851,7 @@ class Channel extends SnowFlake{ await this.buildmessages(); //loading.classList.remove("loading"); (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; + (document.getElementById("typebox") as HTMLDivElement).focus(); } typingmap: Map = new Map(); async typingStart(typing: startTypingjson): Promise{ diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 3e41b5e8..15dbed05 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -197,6 +197,7 @@ class Group extends Channel{ } this.buildmessages(); (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; + (document.getElementById("typebox") as HTMLDivElement).focus(); } messageCreate(messagep: { d: messagejson }){ const messagez = new Message(messagep.d, this); diff --git a/src/webpage/index.html b/src/webpage/index.html index f4ac781c..46c01977 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -75,7 +75,7 @@

Server Name

-
+
diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 6b1cb436..4ab27cf4 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -153,36 +153,30 @@ import { I18n } from "./i18n.js"; if(thisUser.keyup(event)){return} const channel = thisUser.channelfocus; if(!channel)return; - + if(markdown.rawString===""&&event.key==="ArrowUp"){ + channel.editLast(); + return; + } channel.typingstart(); if(event.key === "Enter" && !event.shiftKey){ event.preventDefault(); - - if(channel.editing){ - channel.editing.edit(markdown.rawString); - channel.editing = null; - }else{ - replyingTo = thisUser.channelfocus - ? thisUser.channelfocus.replyingto - : null; - if(replyingTo?.div){ - replyingTo.div.classList.remove("replying"); - } - if(thisUser.channelfocus){ - thisUser.channelfocus.replyingto = null; - } - channel.sendMessage(markdown.rawString, { - attachments: images, - // @ts-ignore This is valid according to the API - embeds: [], // Add an empty array for the embeds property - replyingto: replyingTo, - }); - if(thisUser.channelfocus){ - thisUser.channelfocus.makereplybox(); - } + replyingTo = thisUser.channelfocus? thisUser.channelfocus.replyingto: null; + if(replyingTo?.div){ + replyingTo.div.classList.remove("replying"); + } + if(thisUser.channelfocus){ + thisUser.channelfocus.replyingto = null; + } + channel.sendMessage(markdown.rawString, { + attachments: images, + // @ts-ignore This is valid according to the API + embeds: [], // Add an empty array for the embeds property + replyingto: replyingTo, + }); + if(thisUser.channelfocus){ + thisUser.channelfocus.makereplybox(); } - while(images.length){ images.pop(); pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 489acc12..14637fcf 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1717,34 +1717,32 @@ class Localuser{ Bot.InviteMaker(appId,form,this.info); }) } - typeMd?:MarkDown; readonly autofillregex=Object.freeze(/[@#:]([a-z0-9 ]*)$/i); mdBox(){ interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - this.typeMd=typebox.markdown; - this.typeMd.owner=this; - this.typeMd.onUpdate=this.search.bind(this); + const typeMd=typebox.markdown; + typeMd.owner=this; + typeMd.onUpdate=(str,pre)=>{ + this.search(document.getElementById("searchOptions") as HTMLDivElement,typeMd,str,pre); + } } - MDReplace(replacewith:string,original:string){ - const typebox = document.getElementById("typebox") as HTMLDivElement; - if(!this.typeMd)return; - let raw=this.typeMd.rawString; + MDReplace(replacewith:string,original:string,typebox:MarkDown){ + let raw=typebox.rawString; raw=raw.split(original)[1]; if(raw===undefined) return; raw=original.replace(this.autofillregex,"")+replacewith+raw; console.log(raw); console.log(replacewith); console.log(original); - this.typeMd.txt = raw.split(""); + typebox.txt = raw.split(""); const match=original.match(this.autofillregex); if(match){ - this.typeMd.boxupdate(typebox,replacewith.length-match[0].length); + typebox.boxupdate(replacewith.length-match[0].length); } } - MDSearchOptions(options:[string,string,void|HTMLElement][],original:string){ - const div=document.getElementById("searchOptions"); + MDSearchOptions(options:[string,string,void|HTMLElement][],original:string,div:HTMLDivElement,typebox:MarkDown){ if(!div)return; div.innerHTML=""; let i=0; @@ -1757,7 +1755,6 @@ class Localuser{ const span=document.createElement("span"); htmloptions.push(span); if(thing[2]){ - console.log(thing); span.append(thing[2]); } @@ -1766,19 +1763,21 @@ class Localuser{ if(e){ const selection = window.getSelection() as Selection; - const typebox = document.getElementById("typebox") as HTMLDivElement; + const box=typebox.box.deref(); + if(!box) return; if(selection){ console.warn(original); - const pos = getTextNodeAtPosition(typebox, original.length-(original.match(this.autofillregex) as RegExpMatchArray)[0].length+thing[1].length); + + const pos = getTextNodeAtPosition(box, original.length-(original.match(this.autofillregex) as RegExpMatchArray)[0].length+thing[1].length); selection.removeAllRanges(); const range = new Range(); range.setStart(pos.node, pos.position); selection.addRange(range); } e.preventDefault(); - typebox.focus(); + box.focus(); } - this.MDReplace(thing[1],original); + this.MDReplace(thing[1],original,typebox); div.innerHTML=""; remove(); } @@ -1837,7 +1836,7 @@ class Localuser{ remove(); } } - MDFindChannel(name:string,orginal:string){ + MDFindChannel(name:string,orginal:string,box:HTMLDivElement,typebox:MarkDown){ const maybe:[number,Channel][]=[]; if(this.lookingguild&&this.lookingguild.id!=="@me"){ for(const channel of this.lookingguild.channels){ @@ -1848,7 +1847,7 @@ class Localuser{ } } maybe.sort((a,b)=>b[0]-a[0]); - this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `,undefined]),orginal); + this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `,undefined]),orginal,box,typebox); } async getUser(id:string){ if(this.userMap.has(id)){ @@ -1856,7 +1855,7 @@ class Localuser{ } return new User(await (await fetch(this.info.api+"/users/"+id)).json(),this); } - MDFineMentionGen(name:string,original:string){ + MDFineMentionGen(name:string,original:string,box:HTMLDivElement,typebox:MarkDown){ let members:[Member,number][]=[]; if(this.lookingguild){ for(const member of this.lookingguild.members){ @@ -1867,11 +1866,11 @@ class Localuser{ } } members.sort((a,b)=>b[1]-a[1]); - this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `,undefined]),original); + this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `,undefined]),original,box,typebox); } - MDFindMention(name:string,original:string){ + MDFindMention(name:string,original:string,box:HTMLDivElement,typebox:MarkDown){ if(this.ws&&this.lookingguild){ - this.MDFineMentionGen(name,original); + this.MDFineMentionGen(name,original,box,typebox); const nonce=Math.floor(Math.random()*10**8)+""; if(this.lookingguild.member_count<=this.lookingguild.members.size) return; this.ws.send(JSON.stringify( @@ -1906,19 +1905,19 @@ class Localuser{ await Member.new(thing,this.lookingguild as Guild) } } - this.MDFineMentionGen(name,original); + this.MDFineMentionGen(name,original,box,typebox); } }) } } - findEmoji(search:string,orginal:string){ + findEmoji(search:string,orginal:string,box:HTMLDivElement,typebox:MarkDown){ const emj=Emoji.searchEmoji(search,this,10); const map=emj.map(([emoji]):[string,string,HTMLElement]=>{ return [emoji.name,emoji.id?`<${emoji.animated?"a":""}:${emoji.name}:${emoji.id}>`:emoji.emoji as string,emoji.getHTML()] }) - this.MDSearchOptions(map,orginal); + this.MDSearchOptions(map,orginal,box,typebox); } - search(str:string,pre:boolean){ + search(box:HTMLDivElement,md:MarkDown,str:string,pre:boolean){ if(!pre){ const match=str.match(this.autofillregex); @@ -1926,25 +1925,23 @@ class Localuser{ const [type, search]=[match[0][0],match[0].split(/@|#|:/)[1]]; switch(type){ case "#": - this.MDFindChannel(search,str); + this.MDFindChannel(search,str,box,md); break; case "@": - this.MDFindMention(search,str); + this.MDFindMention(search,str,box,md); break; case ":": if(search.length>=2){ - this.findEmoji(search,str) + this.findEmoji(search,str,box,md) }else{ - this.MDSearchOptions([],""); + this.MDSearchOptions([],"",box,md); } break; } return } } - const div=document.getElementById("searchOptions"); - if(!div)return; - div.innerHTML=""; + box.innerHTML=""; } keydown:(event:KeyboardEvent)=>unknown=()=>{}; keyup:(event:KeyboardEvent)=>boolean=()=>false; diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 0baa143c..13f8167e 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -686,7 +686,9 @@ txt[j + 1] === undefined) e.target.classList.add("unspoiled"); } onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}; + box=new WeakRef(document.createElement("div")); giveBox(box: HTMLDivElement,onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}){ + this.box=new WeakRef(box); this.onUpdate=onUpdate; box.onkeydown = _=>{ //console.log(_); @@ -697,7 +699,7 @@ txt[j + 1] === undefined) if(content !== prevcontent){ prevcontent = content; this.txt = content.split(""); - this.boxupdate(box); + this.boxupdate(); MarkDown.gatherBoxText(box); } @@ -713,7 +715,9 @@ txt[j + 1] === undefined) box.onkeyup(new KeyboardEvent("_")); }; } - boxupdate(box: HTMLElement,offset=0){ + boxupdate(offset=0){ + const box=this.box.deref(); + if(!box) return; const restore = saveCaretPosition(box,offset); box.innerHTML = ""; box.append(this.makeHTML({ keep: true })); @@ -832,85 +836,91 @@ let formatted=false; function saveCaretPosition(context: HTMLElement,offset=0){ const selection = window.getSelection() as Selection; if(!selection)return; - const range = selection.getRangeAt(0); - let base=selection.anchorNode as Node; - range.setStart(base, 0); - let baseString:string; - if(!(base instanceof Text)){ - let i=0; - const index=selection.focusOffset; - //@ts-ignore - for(const thing of base.childNodes){ - if(i===index){ - base=thing; - break; + try{ + const range = selection.getRangeAt(0); + + let base=selection.anchorNode as Node; + range.setStart(base, 0); + let baseString:string; + if(!(base instanceof Text)){ + let i=0; + const index=selection.focusOffset; + //@ts-ignore + for(const thing of base.childNodes){ + if(i===index){ + base=thing; + break; + } + i++; + } + if(base instanceof HTMLElement){ + baseString=MarkDown.gatherBoxText(base) + }else{ + baseString=base.textContent as string; } - i++; - } - if(base instanceof HTMLElement){ - baseString=MarkDown.gatherBoxText(base) }else{ - baseString=base.textContent as string; + baseString=selection.toString(); } - }else{ - baseString=selection.toString(); - } - range.setStart(context, 0); + range.setStart(context, 0); - let build=""; - //I think this is working now :3 - function crawlForText(context:Node){ - //@ts-ignore - const children=[...context.childNodes]; - if(children.length===1&&children[0] instanceof Text){ - if(selection.containsNode(context,false)){ - build+=MarkDown.gatherBoxText(context as HTMLElement); - }else if(selection.containsNode(context,true)){ - if(context.contains(base)||context===base||base.contains(context)){ - build+=baseString; + let build=""; + //I think this is working now :3 + function crawlForText(context:Node){ + //@ts-ignore + const children=[...context.childNodes]; + if(children.length===1&&children[0] instanceof Text){ + if(selection.containsNode(context,false)){ + build+=MarkDown.gatherBoxText(context as HTMLElement); + }else if(selection.containsNode(context,true)){ + if(context.contains(base)||context===base||base.contains(context)){ + build+=baseString; + }else{ + build+=context.textContent; + } }else{ - build+=context.textContent; + console.error(context); } - }else{ - console.error(context); + return; } - return; - } - for(const node of children as Node[]){ + for(const node of children as Node[]){ - if(selection.containsNode(node,false)){ - if(node instanceof HTMLElement){ - build+=MarkDown.gatherBoxText(node); - }else{ - build+=node.textContent; - } - }else if(selection.containsNode(node,true)){ - if(node instanceof HTMLElement){ - crawlForText(node); + if(selection.containsNode(node,false)){ + if(node instanceof HTMLElement){ + build+=MarkDown.gatherBoxText(node); + }else{ + build+=node.textContent; + } + }else if(selection.containsNode(node,true)){ + if(node instanceof HTMLElement){ + crawlForText(node); + }else{ + console.error(node,"This shouldn't happen") + } }else{ - console.error(node,"This shouldn't happen") + //console.error(node,"This shouldn't happen"); } - }else{ - console.error(node,"This shouldn't happen"); } } + crawlForText(context); + if(baseString==="\n"){ + build+=baseString; + } + text=build; + let len=build.length+offset; + len=Math.min(len,MarkDown.gatherBoxText(context).length) + return function restore(){ + if(!selection)return; + const pos = getTextNodeAtPosition(context, len); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node, pos.position); + selection.addRange(range); + }; + }catch{ + return undefined; } - crawlForText(context); - if(baseString==="\n"){ - build+=baseString; - } - text=build; - const len=build.length+offset; - return function restore(){ - if(!selection)return; - const pos = getTextNodeAtPosition(context, len); - selection.removeAllRanges(); - const range = new Range(); - range.setStart(pos.node, pos.position); - selection.addRange(range); - }; } function getTextNodeAtPosition(root: Node, index: number):{ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 2296c7a6..e1dca436 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1,7 +1,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ User }from"./user.js"; import{ Member }from"./member.js"; -import{ MarkDown }from"./markdown.js"; +import{ MarkDown, saveCaretPosition }from"./markdown.js"; import{ Embed }from"./embed.js"; import{ Channel }from"./channel.js"; import{ Localuser }from"./localuser.js"; @@ -96,14 +96,10 @@ class Message extends SnowFlake{ ); } setEdit(){ + const prev=this.channel.editing; this.channel.editing = this; - const markdown = ( - document.getElementById("typebox") as HTMLDivElement & { - markdown: MarkDown; - } - ).markdown as MarkDown; - markdown.txt = this.content.rawString.split(""); - markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); + if(prev) prev.generateMessage(); + this.generateMessage(undefined,false) } constructor(messagejson: messagejson, owner: Channel){ super(messagejson.id); @@ -340,6 +336,7 @@ class Message extends SnowFlake{ } generateMessage(premessage?: Message | undefined, ignoredblock = false){ if(!this.div)return; + const editmode=this.channel.editing===this; if(!premessage){ premessage = this.channel.messages.get( this.channel.idToPrev.get(this.id) as string @@ -476,8 +473,7 @@ class Message extends SnowFlake{ const newt = new Date(this.timestamp).getTime() / 1000; current = newt - old > 600; } - const combine = - premessage?.author != this.author || current || this.message_reference; + const combine = premessage?.author != this.author || current || this.message_reference; if(combine){ const pfp = this.author.buildpfp(); this.author.bind(pfp, this.guild, false); @@ -526,13 +522,56 @@ class Message extends SnowFlake{ }else{ div.classList.remove("topMessage"); } - const messaged = this.content.makeHTML(); - (div as any).txt = messaged; const messagedwrap = document.createElement("div"); - messagedwrap.classList.add("flexttb"); - messagedwrap.appendChild(messaged); - text.appendChild(messagedwrap); + if(editmode){ + const box=document.createElement("div"); + box.classList.add("messageEditContainer"); + const area=document.createElement("div"); + const sb=document.createElement("div"); + sb.style.position="absolute"; + sb.style.width="100%"; + const search=document.createElement("div"); + search.classList.add("searchOptions","flexttb"); + area.classList.add("editMessage"); + area.contentEditable="true"; + const md=new MarkDown(this.content.rawString,this.owner) + area.append(md.makeHTML()); + area.addEventListener("keyup", (event)=>{ + if(this.localuser.keyup(event)) return; + if(event.key === "Enter" && !event.shiftKey){ + this.edit(md.rawString); + this.channel.editing=null; + this.generateMessage(); + } + }); + area.addEventListener("keydown", event=>{ + this.localuser.keydown(event); + if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); + if(event.key === "Escape"){ + this.channel.editing=null; + this.generateMessage(); + } + }); + md.giveBox(area,(str,pre)=>{ + this.localuser.search(search,md,str,pre) + }) + sb.append(search); + box.append(sb,area); + messagedwrap.append(box); + setTimeout(()=>{ + area.focus(); + const fun=saveCaretPosition(area,Infinity); + if(fun) fun(); + }) + }else{ + this.content.onUpdate=()=>{}; + const messaged = this.content.makeHTML(); + (div as any).txt = messaged; + messagedwrap.classList.add("flexttb"); + messagedwrap.appendChild(messaged); + } + text.appendChild(messagedwrap); build.appendChild(text); if(this.attachments.length){ console.log(this.attachments); diff --git a/src/webpage/style.css b/src/webpage/style.css index 46fcff03..b34ad09a 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -21,7 +21,7 @@ body { display: flex; flex-direction: column; } -#searchOptions{ +.searchOptions{ padding:.05in .15in; border-radius: .1in; background: var(--channels-bg); @@ -46,15 +46,17 @@ body { span:hover{ background:var(--button-bg); } - -; margin: 16px; border: solid .025in var(--black); } -#searchOptions:empty{ +.searchOptions:empty{ padding: 0; border: 0; } +.messageEditContainer{ + position: relative; + width:100%; +} .flexgrow { flex-grow: 1; min-height: 0; @@ -268,6 +270,11 @@ textarea { transition: opacity .2s; border: solid .03in var(--black); } +.editMessage{ + background: var(--typebox-bg); + padding: .05in; + border-radius: .04in; +} /* Animations */ @keyframes fade { 0%, 100% { @@ -1004,6 +1011,7 @@ span.instanceStatus { .commentrow { word-break: break-word; gap: 4px; + width: 100%; } .username { margin-top: auto; From 04f634a7087d970afb83107fd223fb66a39eeb05 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 14:56:05 -0600 Subject: [PATCH 0078/1330] fix dep problems --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 47f8481e..1b889083 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "express": "^4.19.2", "gulp-sourcemaps": "^3.0.0", "gulp-swc": "^2.2.0", - "rimraf": "^6.0.1", - "ts-to-jsdoc": "^2.2.0" + "rimraf": "^6.0.1" }, "devDependencies": { "@html-eslint/parser": "^0.27.0", @@ -28,7 +27,6 @@ "@rsbuild/core": "^1.1.4", "@rsbuild/plugin-node-polyfill": "^1.2.0", "@swc/core": "^1.7.26", - "rspack": "^0.1.1", "swc": "^1.0.11", "@eslint/js": "^9.10.0", "@html-eslint/eslint-plugin": "^0.25.0", From 906f4a51d65f758768ca4fd6f2c9336dd05b9042 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 15:29:01 -0600 Subject: [PATCH 0079/1330] add history state navigation --- src/webpage/channel.ts | 7 ++++--- src/webpage/direct.ts | 2 +- src/webpage/guild.ts | 8 ++++---- src/webpage/index.ts | 7 ++++++- src/webpage/localuser.ts | 4 ++-- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 95b6f05c..aee41a72 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -801,7 +801,7 @@ class Channel extends SnowFlake{ } } static genid: number = 0; - async getHTML(){ + async getHTML(addstate=true){ const id = ++Channel.genid; if(this.localuser.channelfocus){ this.localuser.channelfocus.infinite.delete(); @@ -820,8 +820,9 @@ class Channel extends SnowFlake{ this.localuser.userinfo.updateLocal(); this.localuser.channelfocus = this; const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); - + if(addstate){ + history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); + } this.localuser.pageTitle("#" + this.name); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; if(this.topic){ diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 15dbed05..f3c99a60 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -180,7 +180,7 @@ class Group extends Channel{ this.guild.prevchannel = this; this.localuser.channelfocus = this; const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); + history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); this.localuser.pageTitle("@" + this.name); (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index bf72d7d1..688a4af4 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -584,22 +584,22 @@ class Guild extends SnowFlake{ } return this.member.hasRole(r); } - loadChannel(ID?: string | undefined){ + loadChannel(ID?: string | undefined,addstate=true){ if(ID){ const channel = this.localuser.channelids.get(ID); if(channel){ - channel.getHTML(); + channel.getHTML(addstate); return; } } if(this.prevchannel){ console.log(this.prevchannel); - this.prevchannel.getHTML(); + this.prevchannel.getHTML(addstate); return; } for(const thing of this.channels){ if(thing.children.length === 0){ - thing.getHTML(); + thing.getHTML(addstate); return; } } diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 4ab27cf4..40e2fa69 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -148,7 +148,12 @@ import { I18n } from "./i18n.js"; const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement; let replyingTo: Message | null = null; - + window.addEventListener("popstate",(e)=>{ + if(e.state instanceof Object){ + thisUser.goToChannel(e.state[1],false); + } + //console.log(e.state,"state:3") + }) async function handleEnter(event: KeyboardEvent): Promise{ if(thisUser.keyup(event)){return} const channel = thisUser.channelfocus; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 14637fcf..26863ac0 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -726,12 +726,12 @@ class Localuser{ } } gotoid: string | undefined; - async goToChannel(id: string){ + async goToChannel(id: string,addstate=true){ const channel = this.channelids.get(id); if(channel){ const guild = channel.guild; guild.loadGuild(); - guild.loadChannel(id); + guild.loadChannel(id,addstate); }else{ this.gotoid = id; } From 277cf125c2ec5d844081b68b02f506bbe7614c14 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 15:32:06 -0600 Subject: [PATCH 0080/1330] fix bug with dms --- src/webpage/direct.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index f3c99a60..e6e1addf 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -169,7 +169,7 @@ class Group extends Channel{ return div; } - async getHTML(){ + async getHTML(addstate=true){ const id = ++Channel.genid; if(this.localuser.channelfocus){ this.localuser.channelfocus.infinite.delete(); @@ -180,7 +180,9 @@ class Group extends Channel{ this.guild.prevchannel = this; this.localuser.channelfocus = this; const prom = this.infinite.delete(); - history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); + if(addstate){ + history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); + } this.localuser.pageTitle("@" + this.name); (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); From ff443ea1f04868e256ff1720f87a747cba5927fc Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 15:41:56 -0600 Subject: [PATCH 0081/1330] fix formating --- src/webpage/guild.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 688a4af4..dfdb5ca2 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -7,14 +7,7 @@ import{ Member }from"./member.js"; import{ Settings }from"./settings.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; -import{ - channeljson, - guildjson, - emojijson, - memberjson, - invitejson, - rolesjson, -}from"./jsontypes.js"; +import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson,}from"./jsontypes.js"; import{ User }from"./user.js"; import { I18n } from "./i18n.js"; From 2c398bbd8dc02d2141ed7902d68b4aebc7f9ba4a Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 18:51:09 -0600 Subject: [PATCH 0082/1330] faster theme loading --- src/webpage/login.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 4705b033..b79d8196 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -80,7 +80,7 @@ const instancefetch=fetch("/instances.json") } } ); - +setTheme(); await I18n.done function setTheme(){ let name = localStorage.getItem("theme"); @@ -107,7 +107,7 @@ function setTheme(){ noAccount.textContent=I18n.getTranslation("htmlPages.noAccount"); } })() -setTheme(); + function getBulkUsers(){ const json = getBulkInfo(); for(const thing in json.users){ From fb26f77366ae5ce139cbfdbf969cf97c2bda319f Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 19:27:40 -0600 Subject: [PATCH 0083/1330] add reaction button --- src/webpage/icons/emoji.svg | 1 + src/webpage/message.ts | 12 ++++++++++++ src/webpage/style.css | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 src/webpage/icons/emoji.svg diff --git a/src/webpage/icons/emoji.svg b/src/webpage/icons/emoji.svg new file mode 100644 index 00000000..1683d85f --- /dev/null +++ b/src/webpage/icons/emoji.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/message.ts b/src/webpage/message.ts index e1dca436..6f0d78a0 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -641,6 +641,18 @@ class Message extends SnowFlake{ this.channel.setReplying(this); }; } + if(this.channel.hasPermission("ADD_REACTIONS")){ + const container = document.createElement("button"); + const reply = document.createElement("span"); + reply.classList.add("svg-emoji", "svgicon"); + container.append(reply); + buttons.append(container); + container.onclick = e=>{ + Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{ + this.reactionToggle(_); + }); + }; + } if(this.author === this.localuser.user){ const container = document.createElement("button"); const edit = document.createElement("span"); diff --git a/src/webpage/style.css b/src/webpage/style.css index b34ad09a..ddb84cde 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -210,6 +210,9 @@ textarea { .svg-announce { mask: url(/icons/announce.svg); } +.svg-emoji { + mask: url(/icons/emoji.svg); +} .svg-edit { mask: url(/icons/edit.svg); } From d739dc0a3b08bed9a0b562aec87d6b7c2dda2e3d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 25 Nov 2024 20:32:00 -0600 Subject: [PATCH 0084/1330] more file upload options --- src/webpage/channel.ts | 1 + src/webpage/direct.ts | 1 + src/webpage/icons/upload.svg | 1 + src/webpage/index.html | 8 +- src/webpage/index.ts | 147 +++++++++++++++++++++++++---------- src/webpage/style.css | 26 ++++++- translations/en.json | 1 + 7 files changed, 141 insertions(+), 44 deletions(-) create mode 100644 src/webpage/icons/upload.svg diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index aee41a72..62c383ca 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -852,6 +852,7 @@ class Channel extends SnowFlake{ await this.buildmessages(); //loading.classList.remove("loading"); (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; + (document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; (document.getElementById("typebox") as HTMLDivElement).focus(); } typingmap: Map = new Map(); diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index e6e1addf..1b582a12 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -199,6 +199,7 @@ class Group extends Channel{ } this.buildmessages(); (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; + (document.getElementById("upload") as HTMLElement).style.visibility="visible"; (document.getElementById("typebox") as HTMLDivElement).focus(); } messageCreate(messagep: { d: messagejson }){ diff --git a/src/webpage/icons/upload.svg b/src/webpage/icons/upload.svg new file mode 100644 index 00000000..6c72ded1 --- /dev/null +++ b/src/webpage/icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/index.html b/src/webpage/index.html index 46c01977..8a05715a 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -81,7 +81,10 @@

Server Name

-
+
+ +
+
+ diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 40e2fa69..9099fbaf 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -3,7 +3,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js"; import{ MarkDown }from"./markdown.js"; import{ Message }from"./message.js"; -import{ File }from"./file.js"; +import{File}from"./file.js"; import { I18n } from "./i18n.js"; (async ()=>{ @@ -17,10 +17,12 @@ import { I18n } from "./i18n.js"; const loadingText=document.getElementById("loadingText"); const loaddesc=document.getElementById("load-desc"); const switchaccounts=document.getElementById("switchaccounts"); - if(loadingText&&loaddesc&&switchaccounts){ + const filedroptext=document.getElementById("filedroptext"); + if(loadingText&&loaddesc&&switchaccounts&&filedroptext){ loadingText.textContent=I18n.getTranslation("htmlPages.loadingText"); loaddesc.textContent=I18n.getTranslation("htmlPages.loaddesc"); switchaccounts.textContent=I18n.getTranslation("htmlPages.switchaccounts"); + filedroptext.textContent=I18n.getTranslation("uploadFilesText"); } } I18n @@ -191,50 +193,115 @@ import { I18n } from "./i18n.js"; } } - interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} + interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - const markdown = new MarkDown("", thisUser); - typebox.markdown = markdown; - typebox.addEventListener("keyup", handleEnter); - typebox.addEventListener("keydown", event=>{ - thisUser.keydown(event) - if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); - }); - markdown.giveBox(typebox); + const typebox = document.getElementById("typebox") as CustomHTMLDivElement; + const markdown = new MarkDown("", thisUser); + typebox.markdown = markdown; + typebox.addEventListener("keyup", handleEnter); + typebox.addEventListener("keydown", event=>{ + thisUser.keydown(event) + if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); + }); + markdown.giveBox(typebox); - const images: Blob[] = []; - const imagesHtml: HTMLElement[] = []; + const images: Blob[] = []; + const imagesHtml: HTMLElement[] = []; - document.addEventListener("paste", async (e: ClipboardEvent)=>{ - if(!e.clipboardData)return; + document.addEventListener("paste", async (e: ClipboardEvent)=>{ + if(!e.clipboardData)return; - for(const file of Array.from(e.clipboardData.files)){ - const fileInstance = File.initFromBlob(file); - e.preventDefault(); - const html = fileInstance.upHTML(images, file); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.push(html); - } - }); + for(const file of Array.from(e.clipboardData.files)){ + const fileInstance = File.initFromBlob(file); + e.preventDefault(); + const html = fileInstance.upHTML(images, file); + pasteImageElement.appendChild(html); + images.push(file); + imagesHtml.push(html); + } + }); - setTheme(); + setTheme(); - function userSettings(): void{ - thisUser.showusersettings(); - } + function userSettings(): void{ + thisUser.showusersettings(); + } + + (document.getElementById("settings") as HTMLImageElement).onclick = + userSettings; + + if(mobile){ + const channelWrapper = document.getElementById("channelw") as HTMLDivElement; + channelWrapper.onclick = ()=>{ + const toggle = document.getElementById("maintoggle") as HTMLInputElement; + toggle.checked = true; + }; + const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement; + memberListToggle.checked = false; + } + let dragendtimeout=setTimeout(()=>{}) + document.addEventListener("dragover",(e)=>{ + clearTimeout(dragendtimeout); + const data = e.dataTransfer; + const bg=document.getElementById("gimmefile") as HTMLDivElement; - (document.getElementById("settings") as HTMLImageElement).onclick = - userSettings; - - if(mobile){ - const channelWrapper = document.getElementById("channelw") as HTMLDivElement; - channelWrapper.onclick = ()=>{ - const toggle = document.getElementById("maintoggle") as HTMLInputElement; - toggle.checked = true; - }; - const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement; - memberListToggle.checked = false; + if(data){ + const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file"); + if(!isfile){ + bg.hidden=true; + return; + } + e.preventDefault(); + bg.hidden=false; + //console.log(data.types,data) + }else{ + bg.hidden=true; } + }); + document.addEventListener("dragleave",(_)=>{ + dragendtimeout=setTimeout(()=>{ + const bg=document.getElementById("gimmefile") as HTMLDivElement; + bg.hidden=true; + },1000) + }); + document.addEventListener("dragenter",(e)=>{ + e.preventDefault(); + }) + document.addEventListener("drop",e=>{ + const data = e.dataTransfer; + const bg=document.getElementById("gimmefile") as HTMLDivElement; + bg.hidden=true; + if(data){ + const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file"); + if(isfile){ + e.preventDefault(); + console.log(data.files); + for(const file of Array.from(data.files)){ + const fileInstance = File.initFromBlob(file); + const html = fileInstance.upHTML(images, file); + pasteImageElement.appendChild(html); + images.push(file); + imagesHtml.push(html); + } + } + } + }); + (document.getElementById("upload") as HTMLElement).onclick=()=>{ + const input=document.createElement("input"); + input.type="file"; + input.click(); + console.log("clicked") + input.onchange=(() => { + if(input.files){ + for(const file of Array.from(input.files)){ + const fileInstance = File.initFromBlob(file); + const html = fileInstance.upHTML(images, file); + pasteImageElement.appendChild(html); + images.push(file); + imagesHtml.push(html); + } + } + }) + } + })(); diff --git a/src/webpage/style.css b/src/webpage/style.css index ddb84cde..ba652d99 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -246,6 +246,11 @@ textarea { .svg-plus { mask: url(/icons/plus.svg); } +.svg-upload { + mask: url(/icons/upload.svg); + width: .2in !important;!i;!; + cursor: pointer; +} .svg-x { mask: url(/icons/x.svg); } @@ -278,6 +283,13 @@ textarea { padding: .05in; border-radius: .04in; } +#gimmefile{ + position: absolute; + width: 100%; + height: 100%; + background: #00000070; + top:0px; +} /* Animations */ @keyframes fade { 0%, 100% { @@ -832,6 +844,7 @@ span.instanceStatus { display: flex; gap: 12px; overflow-y: auto; + flex-wrap: wrap; } #pasteimage:empty { height: 0; @@ -885,14 +898,21 @@ span.instanceStatus { #realbox { padding: 0 16px 28px 16px; } -#typebox { +#typebox{ + flex-grow:1; + width:100%; + margin-left: .06in; +} +.outerTypeBox { max-height: 50svh; - padding: 10px 14px; + padding: 10px 10px; background: var(--typebox-bg); border-radius: 4px; overflow-y: auto; + display:flex; + flex-direction: row; } -#typebox > span::before { +.outerTypeBox > span::before { content: "\feff"; } #typebox[contenteditable=false] { diff --git a/translations/en.json b/translations/en.json index 781c4750..370939b4 100644 --- a/translations/en.json +++ b/translations/en.json @@ -375,6 +375,7 @@ "reason:":"Reason:", "ban":"Ban $1 from $2" }, + "uploadFilesText":"Upload your files here!", "errorReconnect":"Unable to connect to the server, retrying in **$1** seconds...", "retrying":"Retrying...", "unableToConnect":"Unable to connect to the Spacebar server. Please try logging out and back in." From 476a64d914eddc05db361b2d1078ac7d679437a4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 26 Nov 2024 08:41:14 -0600 Subject: [PATCH 0085/1330] messaging editing bug --- src/webpage/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 6f0d78a0..44f83b28 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -534,7 +534,7 @@ class Message extends SnowFlake{ search.classList.add("searchOptions","flexttb"); area.classList.add("editMessage"); area.contentEditable="true"; - const md=new MarkDown(this.content.rawString,this.owner) + const md=new MarkDown(this.content.rawString,this.owner,{keep:true}) area.append(md.makeHTML()); area.addEventListener("keyup", (event)=>{ if(this.localuser.keyup(event)) return; From c9ce8c234a957276d6b7b9b2b972e759157f2466 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 26 Nov 2024 09:00:33 -0600 Subject: [PATCH 0086/1330] fix message editing --- src/webpage/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 44f83b28..cfac8b3d 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -534,7 +534,7 @@ class Message extends SnowFlake{ search.classList.add("searchOptions","flexttb"); area.classList.add("editMessage"); area.contentEditable="true"; - const md=new MarkDown(this.content.rawString,this.owner,{keep:true}) + const md=new MarkDown(this.content.rawString,this.owner,{keep:true}); area.append(md.makeHTML()); area.addEventListener("keyup", (event)=>{ if(this.localuser.keyup(event)) return; From e24afe2abefbd24a5d936dc6c5dab400bf7a052b Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 26 Nov 2024 15:28:28 -0600 Subject: [PATCH 0087/1330] various updates --- gulpfile.cjs | 150 ++++++++++++++++++---------------- src/webpage/channel.ts | 9 +- src/webpage/direct.ts | 30 +++++-- src/webpage/guild.ts | 62 ++++++++++++-- src/webpage/icons/friends.svg | 1 + src/webpage/index.html | 2 +- src/webpage/index.ts | 1 - src/webpage/localuser.ts | 3 +- src/webpage/style.css | 17 +++- 9 files changed, 180 insertions(+), 95 deletions(-) create mode 100644 src/webpage/icons/friends.svg diff --git a/gulpfile.cjs b/gulpfile.cjs index a1560a69..fd43e3ae 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -8,98 +8,110 @@ const plumber = require("gulp-plumber"); const sourcemaps = require('gulp-sourcemaps'); const fs=require("fs"); const swcOptions = { - jsc: { +jsc: { parser: { - syntax: "typescript", - tsx: false, - decorators: true, - dynamicImport: true, + syntax: "typescript", + tsx: false, + decorators: true, + dynamicImport: true, + }, + transform: { + react: { + runtime: "automatic", + }, + }, + target: "es2022", + loose: false, + externalHelpers: false, + keepClassNames: true, }, - transform: { - react: { - runtime: "automatic", - }, + module: { + type: "es6", + strict: true, + strictMode: true, + lazy: false, + noInterop: false, }, - target: "es2022", - loose: false, - externalHelpers: false, - keepClassNames: true, - }, - module: { - type: "es6", - strict: true, - strictMode: true, - lazy: false, - noInterop: false, - }, - sourceMaps: true, - minify: false, + sourceMaps: true, + minify: false, }; + + +gulp.task('watch', function () { + gulp.watch('./src', gulp.series("default")); +}, {debounceDelay: 10}); + // Clean task to delete the dist directory gulp.task("clean", (cb) => { - return rimraf.rimraf("dist").then(cb()); -}); - -// Task to compile TypeScript files using SWC -gulp.task("scripts", () => { - if (argv.swc) { - return gulp - .src("src/**/*.ts") - .pipe(sourcemaps.init()) - .pipe(plumber()) // Prevent pipe breaking caused by errors - .pipe(swc(swcOptions)) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest("dist")); - } else { - console.warn("[WARN] Using TSC compiler, will be slower than SWC"); - return gulp - .src("src/**/*.ts") - .pipe(sourcemaps.init()) - .pipe(plumber()) // Prevent pipe breaking caused by errors - .pipe(tsProject()) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest("dist")); - } + return rimraf.rimraf("dist").then(cb()); + }); + const exec = require('child_process').exec; + // Task to compile TypeScript files using SWC + gulp.task("scripts", async () => { + if (argv.swc) { + return gulp + .src("src/**/*.ts") + .pipe(sourcemaps.init()) + .pipe(plumber()) // Prevent pipe breaking caused by errors + .pipe(swc(swcOptions)) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest("dist")); + } else if(argv.bunswc){ + return await new Promise(ret=>{ + exec("bun swc ./src -s -d dist").on('exit', function (code) { + ret(); + }); + }) + }else { + console.warn("[WARN] Using TSC compiler, will be slower than SWC"); + return gulp + .src("src/**/*.ts") + .pipe(sourcemaps.init()) + .pipe(plumber()) // Prevent pipe breaking caused by errors + .pipe(tsProject()) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest("dist")); + } }); // Task to copy HTML files gulp.task("copy-html", () => { - return gulp +return gulp .src("src/**/*.html") .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist")); }); gulp.task("copy-translations", () => { - let langs=fs.readdirSync("translations"); - langs=langs.filter((e)=>e!=="qqq.json"); - const langobj={}; - for(const lang of langs){ +let langs=fs.readdirSync("translations"); +langs=langs.filter((e)=>e!=="qqq.json"); +const langobj={}; +for(const lang of langs){ const json=JSON.parse(fs.readFileSync("translations/"+lang).toString()); langobj[lang]=json.readableName; - } - if(!fs.existsSync("dist/webpage/translations")) fs.mkdirSync("dist/webpage/translations") - fs.writeFileSync("dist/webpage/translations/langs.js",`const langs=${JSON.stringify(langobj)};export{langs}`); - return gulp +} +if(!fs.existsSync("dist/webpage/translations")) fs.mkdirSync("dist/webpage/translations") +fs.writeFileSync("dist/webpage/translations/langs.js",`const langs=${JSON.stringify(langobj)};export{langs}`); +return gulp .src("translations/*.json") .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist/webpage/translations")); }); // Task to copy other static assets (e.g., CSS, images) gulp.task("copy-assets", () => { - return gulp +return gulp .src([ - "src/**/*.css", - "src/**/*.bin", - "src/**/*.ico", - "src/**/*.json", - "src/**/*.js", - "src/**/*.png", - "src/**/*.jpg", - "src/**/*.jpeg", - "src/**/*.webp", - "src/**/*.gif", - "src/**/*.svg", + "src/**/*.css", + "src/**/*.bin", + "src/**/*.ico", + "src/**/*.json", + "src/**/*.js", + "src/**/*.png", + "src/**/*.jpg", + "src/**/*.jpeg", + "src/**/*.webp", + "src/**/*.gif", + "src/**/*.svg", ],{encoding:false}) .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist")); @@ -107,6 +119,6 @@ gulp.task("copy-assets", () => { // Default task to run all tasks gulp.task( - "default", - gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets"), "copy-translations") +"default", +gulp.series("clean", gulp.parallel("scripts", "copy-html", "copy-assets"), "copy-translations") ); diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 62c383ca..9e88783e 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -853,6 +853,7 @@ class Channel extends SnowFlake{ //loading.classList.remove("loading"); (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; (document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; (document.getElementById("typebox") as HTMLDivElement).focus(); } typingmap: Map = new Map(); @@ -1086,10 +1087,8 @@ class Channel extends SnowFlake{ let id: string | undefined; if(this.lastreadmessageid && this.messages.has(this.lastreadmessageid)){ id = this.lastreadmessageid; - }else if( - this.lastreadmessageid && - (id = this.findClosest(this.lastreadmessageid)) - ){ + }else if(this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))){ + }else if(this.lastmessageid && this.messages.has(this.lastmessageid)){ id = this.goBackIds(this.lastmessageid, 50); } @@ -1098,7 +1097,7 @@ class Channel extends SnowFlake{ const title = document.createElement("h2"); title.id = "removetitle"; title.textContent = I18n.getTranslation("noMessages"); - title.classList.add("titlespace"); + title.classList.add("titlespace","messagecontainer"); messages.append(title); } this.infinitefocus = false; diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 1b582a12..8e993946 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -3,12 +3,7 @@ import{ Channel }from"./channel.js"; import{ Message }from"./message.js"; import{ Localuser }from"./localuser.js"; import{ User }from"./user.js"; -import{ - channeljson, - dirrectjson, - memberjson, - messagejson, -}from"./jsontypes.js"; +import{channeljson,dirrectjson,memberjson,messagejson}from"./jsontypes.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{ Contextmenu }from"./contextmenu.js"; @@ -23,9 +18,6 @@ class Direct extends Guild{ super(-1, owner, null); this.message_notifications = 0; this.owner = owner; - if(!this.localuser){ - console.error("Owner was not included, please fix"); - } this.headers = this.localuser.headers; this.channels = []; this.channelids = {}; @@ -58,6 +50,25 @@ class Direct extends Guild{ channel.del(); } } + getHTML(){ + const ddiv=document.createElement("div"); + const build=super.getHTML(); + const freindDiv=document.createElement("div"); + freindDiv.classList.add("liststyle","flexltr","friendsbutton"); + + const icon=document.createElement("span"); + icon.classList.add("svgicon","svg-friends","space"); + freindDiv.append(icon); + + freindDiv.append("Friends"); + ddiv.append(freindDiv); + freindDiv.onclick=()=>{ + this.loadChannel(null); + } + + ddiv.append(build); + return ddiv; + } giveMember(_member: memberjson){ console.error("not a real guild, can't give member object"); } @@ -200,6 +211,7 @@ class Group extends Channel{ this.buildmessages(); (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; (document.getElementById("upload") as HTMLElement).style.visibility="visible"; + (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; (document.getElementById("typebox") as HTMLDivElement).focus(); } messageCreate(messagep: { d: messagejson }){ diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index dfdb5ca2..a729a2ce 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -577,7 +577,7 @@ class Guild extends SnowFlake{ } return this.member.hasRole(r); } - loadChannel(ID?: string | undefined,addstate=true){ + loadChannel(ID?: string | undefined| null,addstate=true){ if(ID){ const channel = this.localuser.channelids.get(ID); if(channel){ @@ -585,17 +585,63 @@ class Guild extends SnowFlake{ return; } } - if(this.prevchannel){ + if(this.prevchannel&&ID!==null){ console.log(this.prevchannel); this.prevchannel.getHTML(addstate); return; } - for(const thing of this.channels){ - if(thing.children.length === 0){ - thing.getHTML(addstate); - return; + if(this.id!=="@me"){ + for(const thing of this.channels){ + if(thing.type!==4){ + thing.getHTML(addstate); + return; + } } } + this.removePrevChannel(); + this.noChannel(addstate); + } + removePrevChannel(){ + if(this.localuser.channelfocus){ + this.localuser.channelfocus.infinite.delete(); + } + if(this !== this.localuser.lookingguild){ + this.loadGuild(); + } + if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + this.prevchannel = undefined; + this.localuser.channelfocus = undefined; + const replybox = document.getElementById("replybox") as HTMLElement; + const typebox = document.getElementById("typebox") as HTMLElement; + replybox.classList.add("hideReplyBox"); + typebox.classList.remove("typeboxreplying"); + (document.getElementById("typebox") as HTMLDivElement).contentEditable ="false"; + (document.getElementById("upload") as HTMLElement).style.visibility="hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility="hidden"; + (document.getElementById("sideDiv") as HTMLElement).innerHTML=""; + } + noChannel(addstate:boolean){ + if(addstate){ + history.pushState([this.id,undefined], "", "/channels/" + this.id); + } + this.localuser.pageTitle("Weird spot"); + const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; + channelTopic.setAttribute("hidden", ""); + + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + loading.classList.remove("loading"); + this.localuser.getSidePannel(); + + const messages = document.getElementById("channelw") as HTMLDivElement; + for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){ + thing.remove(); + } + const h1=document.createElement("h1"); + h1.classList.add("messagecontainer") + h1.textContent="You're in a weird spot, this guild has no channels"; + messages.append(h1); } loadGuild(){ this.localuser.loadGuild(this.id); @@ -703,7 +749,9 @@ class Guild extends SnowFlake{ if(indexy !== -1){ this.headchannels.splice(indexy, 1); } - + if(channel===this.prevchannel){ + this.prevchannel=undefined; + } /* const build=[]; for(const thing of this.channels){ diff --git a/src/webpage/icons/friends.svg b/src/webpage/icons/friends.svg new file mode 100644 index 00000000..21b82522 --- /dev/null +++ b/src/webpage/icons/friends.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/index.html b/src/webpage/index.html index 8a05715a..b1933cee 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -64,7 +64,7 @@

Server Name

diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 9099fbaf..6499fcbc 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -5,7 +5,6 @@ import{ MarkDown }from"./markdown.js"; import{ Message }from"./message.js"; import{File}from"./file.js"; import { I18n } from "./i18n.js"; - (async ()=>{ await I18n.done const users = getBulkUsers(); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 26863ac0..0422d500 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -794,8 +794,7 @@ class Localuser{ guild.html.classList.add("serveropen"); } this.lookingguild = guild; - (document.getElementById("serverName") as HTMLElement).textContent = - guild.properties.name; + (document.getElementById("serverName") as HTMLElement).textContent = guild.properties.name; //console.log(this.guildids,id) const channels = document.getElementById("channels") as HTMLDivElement; channels.innerHTML = ""; diff --git a/src/webpage/style.css b/src/webpage/style.css index ba652d99..fb23e4ce 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -248,12 +248,18 @@ textarea { } .svg-upload { mask: url(/icons/upload.svg); - width: .2in !important;!i;!; + width: .2in !important; cursor: pointer; } .svg-x { mask: url(/icons/x.svg); } +.svg-friends{ + mask: url(/icons/friends.svg); + width: 24px !important;!i;!; + height: 24px !important;!i;!; + margin-right: 0 !important;!i;!; +} .svgicon { display: block; height: 100%; @@ -328,6 +334,10 @@ textarea { ::-webkit-scrollbar-thumb:hover { background: var(--primary-text-soft); } +#sideDiv:empty{ + width:0px; + padding:0; +} #servers::-webkit-scrollbar, #channels::-webkit-scrollbar, #sideDiv::-webkit-scrollbar { display: none; } @@ -2054,6 +2064,11 @@ fieldset input[type="radio"] { } } +.friendsbutton{ + transition: background-color .2s; + background-color: #00000050; + padding: .08in; +} .bigemoji{ width:.6in; } From 2502d8f977889333fc786de58132321506c95f64 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 26 Nov 2024 22:30:04 -0600 Subject: [PATCH 0088/1330] friend and more update --- gulpfile.cjs | 1 + src/webpage/channel.ts | 10 +- src/webpage/direct.ts | 244 ++++++++++++++++++++++++++++++-- src/webpage/guild.ts | 4 +- src/webpage/icons/addfriend.svg | 1 + src/webpage/icons/frmessage.svg | 1 + src/webpage/index.html | 2 +- src/webpage/jsontypes.ts | 20 ++- src/webpage/localuser.ts | 20 +++ src/webpage/settings.ts | 92 ++++++++++-- src/webpage/style.css | 70 ++++++++- src/webpage/user.ts | 82 ++++++----- translations/en.json | 23 ++- 13 files changed, 491 insertions(+), 79 deletions(-) create mode 100644 src/webpage/icons/addfriend.svg create mode 100644 src/webpage/icons/frmessage.svg diff --git a/gulpfile.cjs b/gulpfile.cjs index fd43e3ae..f317e6fb 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -40,6 +40,7 @@ jsc: { gulp.task('watch', function () { gulp.watch('./src', gulp.series("default")); + gulp.watch('./translations', gulp.series("default")); }, {debounceDelay: 10}); // Clean task to delete the dist directory diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 9e88783e..01dab529 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -842,6 +842,10 @@ class Channel extends SnowFlake{ if(this.voice&&localStorage.getItem("Voice enabled")){ this.localuser.joinVoice(this); } + (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; + (document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; + (document.getElementById("typebox") as HTMLDivElement).focus(); await this.putmessages(); await prom; if(id !== Channel.genid){ @@ -851,10 +855,7 @@ class Channel extends SnowFlake{ await this.buildmessages(); //loading.classList.remove("loading"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; - (document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; - (document.getElementById("typebox") as HTMLDivElement).focus(); + } typingmap: Map = new Map(); async typingStart(typing: startTypingjson): Promise{ @@ -938,6 +939,7 @@ class Channel extends SnowFlake{ } lastmessage: Message | undefined; async putmessages(){ + //TODO swap out with the WS op code if(this.allthewayup){ return; } diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 8e993946..d3c03244 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -8,9 +8,11 @@ import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{ Contextmenu }from"./contextmenu.js"; import { I18n } from "./i18n.js"; +import { Float, FormError } from "./settings.js"; class Direct extends Guild{ declare channelids: { [key: string]: Group }; + channels: Group[]; getUnixTime(): number{ throw new Error("Do not call this for Direct, it does not make sense"); } @@ -26,7 +28,7 @@ class Direct extends Guild{ this.roles = []; this.roleids = new Map(); this.prevchannel = undefined; - this.properties.name = "Direct Messages"; + this.properties.name = I18n.getTranslation("DMs.name"); for(const thing of json){ const temp = new Group(thing, this); this.channels.push(temp); @@ -60,7 +62,7 @@ class Direct extends Guild{ icon.classList.add("svgicon","svg-friends","space"); freindDiv.append(icon); - freindDiv.append("Friends"); + freindDiv.append(I18n.getTranslation("friends.friends")); ddiv.append(freindDiv); freindDiv.onclick=()=>{ this.loadChannel(null); @@ -69,6 +71,217 @@ class Direct extends Guild{ ddiv.append(build); return ddiv; } + noChannel(addstate:boolean){ + if(addstate){ + history.pushState([this.id,undefined], "", "/channels/" + this.id); + } + this.localuser.pageTitle(I18n.getTranslation("friends.friendlist")); + const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; + channelTopic.removeAttribute("hidden"); + channelTopic.textContent=""; + + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + loading.classList.remove("loading"); + this.localuser.getSidePannel(); + + const messages = document.getElementById("channelw") as HTMLDivElement; + for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){ + thing.remove(); + } + const container=document.createElement("div"); + container.classList.add("messagecontainer","flexttb","friendcontainer") + + messages.append(container); + const checkVoid=()=>{ + if(this.localuser.channelfocus!==undefined||this.localuser.lookingguild!==this){ + this.localuser.relationshipsUpdate=()=>{}; + } + } + function genuserstrip(user:User,icons:HTMLElement):HTMLElement{ + const div=document.createElement("div"); + div.classList.add("flexltr","liststyle"); + user.bind(div); + div.append(user.buildpfp()); + + const userinfos=document.createElement("div"); + userinfos.classList.add("flexttb"); + const username=document.createElement("span"); + username.textContent=user.name; + userinfos.append(username,user.getStatus()); + div.append(userinfos); + User.contextmenu.bindContextmenu(div,user,undefined); + userinfos.style.flexGrow="1"; + + div.append(icons); + return div; + } + { + //TODO update on users coming online + const online=document.createElement("button"); + online.textContent=I18n.getTranslation("friends.online"); + channelTopic.append(online); + const genOnline=()=>{ + this.localuser.relationshipsUpdate=genOnline; + checkVoid(); + container.innerHTML=""; + container.append(I18n.getTranslation("friends.online:")); + for(const user of this.localuser.inrelation){ + if(user.relationshipType===1&&user.online){ + const buttonc=document.createElement("div"); + const button1=document.createElement("span"); + button1.classList.add("svg-frmessage","svgicon"); + buttonc.append(button1); + buttonc.classList.add("friendlyButton"); + buttonc.onclick=(e)=>{ + e.stopImmediatePropagation(); + user.opendm(); + } + container.append(genuserstrip(user,buttonc)); + } + } + } + online.onclick=genOnline; + genOnline(); + } + { + const all=document.createElement("button"); + all.textContent=I18n.getTranslation("friends.all"); + const genAll=()=>{ + this.localuser.relationshipsUpdate=genAll; + checkVoid(); + container.innerHTML=""; + container.append(I18n.getTranslation("friends.all:")); + for(const user of this.localuser.inrelation){ + if(user.relationshipType===1){ + const buttonc=document.createElement("div"); + const button1=document.createElement("span"); + button1.classList.add("svg-frmessage","svgicon"); + buttonc.append(button1); + buttonc.classList.add("friendlyButton"); + buttonc.onclick=(e)=>{ + e.stopImmediatePropagation(); + user.opendm(); + } + container.append(genuserstrip(user,buttonc)); + } + } + } + all.onclick=genAll; + channelTopic.append(all); + } + { + const pending=document.createElement("button"); + pending.textContent=I18n.getTranslation("friends.pending"); + const genPending=()=>{ + this.localuser.relationshipsUpdate=genPending; + checkVoid(); + container.innerHTML=""; + container.append(I18n.getTranslation("friends.pending:")); + for(const user of this.localuser.inrelation){ + if(user.relationshipType===3||user.relationshipType===4){ + const buttons=document.createElement("div"); + buttons.classList.add("flexltr"); + const buttonc=document.createElement("div"); + const button1=document.createElement("span"); + button1.classList.add("svgicon","svg-x"); + if(user.relationshipType===3){ + const buttonc=document.createElement("div"); + const button2=document.createElement("span"); + button2.classList.add("svgicon","svg-x"); + button2.classList.add("svg-addfriend"); + buttonc.append(button2); + buttonc.classList.add("friendlyButton"); + buttonc.append(button2); + buttons.append(buttonc); + buttonc.onclick=(e)=>{ + e.stopImmediatePropagation(); + user.changeRelationship(1); + outerDiv.remove(); + } + } + buttonc.append(button1); + buttonc.classList.add("friendlyButton"); + buttonc.onclick=(e)=>{ + e.stopImmediatePropagation(); + user.changeRelationship(0); + outerDiv.remove(); + } + buttons.append(buttonc); + const outerDiv=genuserstrip(user,buttons); + container.append(outerDiv); + } + } + } + pending.onclick=genPending; + channelTopic.append(pending); + } + { + const blocked=document.createElement("button"); + blocked.textContent=I18n.getTranslation("friends.blocked"); + + const genBlocked=()=>{ + this.localuser.relationshipsUpdate=genBlocked; + checkVoid(); + container.innerHTML=""; + container.append(I18n.getTranslation("friends.blockedusers")); + for(const user of this.localuser.inrelation){ + if(user.relationshipType===2){ + const buttonc=document.createElement("div"); + const button1=document.createElement("span"); + button1.classList.add("svg-x","svgicon"); + buttonc.append(button1); + buttonc.classList.add("friendlyButton"); + buttonc.onclick=(e)=>{ + user.changeRelationship(0); + e.stopImmediatePropagation(); + outerDiv.remove(); + } + const outerDiv=genuserstrip(user,buttonc); + container.append(outerDiv); + } + } + } + blocked.onclick=genBlocked; + channelTopic.append(blocked); + } + { + const add=document.createElement("button"); + add.textContent=I18n.getTranslation("friends.addfriend"); + add.onclick=()=>{ + this.localuser.relationshipsUpdate=()=>{}; + container.innerHTML=""; + const float=new Float(""); + const options=float.options; + const form=options.addForm("",(e:any)=>{ + console.log(e); + if(e.code===404){ + throw new FormError(text,I18n.getTranslation("friends.notfound")); + }else if(e.code===400){ + throw new FormError(text,e.message.split("Error: ")[1]); + }else{ + const box=text.input.deref(); + if(!box)return; + box.value=""; + } + },{ + method:"POST", + fetchURL:this.info.api+"/users/@me/relationships", + headers:this.headers + }); + const text=form.addTextInput(I18n.getTranslation("friends.addfriendpromt"),"username"); + form.addPreprocessor((obj:any)=>{ + const [username,discriminator]=obj.username.split("#"); + obj.username=username; + obj.discriminator=discriminator; + if(!discriminator){ + throw new FormError(text,I18n.getTranslation("friends.discnotfound")); + } + }); + container.append(float.generateHTML()); + } + channelTopic.append(add); + } + } giveMember(_member: memberjson){ console.error("not a real guild, can't give member object"); } @@ -143,15 +356,15 @@ class Group extends Channel{ this.user = this.localuser.user; } this.name ??= this.localuser.user.username; - this.parent_id!; - this.parent!; - this.children = []; - this.guild_id = "@me"; - this.permission_overwrites = new Map(); - this.lastmessageid = json.last_message_id; - this.mentions = 0; - this.setUpInfiniteScroller(); - this.updatePosition(); + this.parent_id!; + this.parent!; + this.children = []; + this.guild_id = "@me"; + this.permission_overwrites = new Map(); + this.lastmessageid = json.last_message_id; + this.mentions = 0; + this.setUpInfiniteScroller(); + this.updatePosition(); } updatePosition(){ if(this.lastmessageid){ @@ -202,6 +415,10 @@ class Group extends Channel{ loading.classList.add("loading"); this.rendertyping(); + (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; + (document.getElementById("upload") as HTMLElement).style.visibility="visible"; + (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; + (document.getElementById("typebox") as HTMLDivElement).focus(); await this.putmessages(); await prom; this.localuser.getSidePannel(); @@ -209,10 +426,7 @@ class Group extends Channel{ return; } this.buildmessages(); - (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; - (document.getElementById("upload") as HTMLElement).style.visibility="visible"; - (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; - (document.getElementById("typebox") as HTMLDivElement).focus(); + } messageCreate(messagep: { d: messagejson }){ const messagez = new Message(messagep.d, this); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index a729a2ce..79e7b7d3 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -626,7 +626,7 @@ class Guild extends SnowFlake{ if(addstate){ history.pushState([this.id,undefined], "", "/channels/" + this.id); } - this.localuser.pageTitle("Weird spot"); + this.localuser.pageTitle(I18n.getTranslation("guild.emptytitle")); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; channelTopic.setAttribute("hidden", ""); @@ -640,7 +640,7 @@ class Guild extends SnowFlake{ } const h1=document.createElement("h1"); h1.classList.add("messagecontainer") - h1.textContent="You're in a weird spot, this guild has no channels"; + h1.textContent=I18n.getTranslation("guild.emptytext"); messages.append(h1); } loadGuild(){ diff --git a/src/webpage/icons/addfriend.svg b/src/webpage/icons/addfriend.svg new file mode 100644 index 00000000..5f34ade3 --- /dev/null +++ b/src/webpage/icons/addfriend.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/icons/frmessage.svg b/src/webpage/icons/frmessage.svg new file mode 100644 index 00000000..efcc42da --- /dev/null +++ b/src/webpage/icons/frmessage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/index.html b/src/webpage/index.html index b1933cee..13f62766 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -59,7 +59,7 @@

Server Name

- + Channel name diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index d9a2d2cb..ae6134ac 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -505,7 +505,25 @@ roleCreate | { op:9, d:boolean, s:number -}|memberlistupdatejson|voiceupdate|voiceserverupdate; +}|memberlistupdatejson|voiceupdate|voiceserverupdate|{ + op: 0, + t: "RELATIONSHIP_ADD", + d: { + id: string, + type: 0|1|2|3|4|5|6, + user: userjson + }, + s: number +}|{ + op: 0, + t: "RELATIONSHIP_REMOVE", + d: { + id: string, + type: number, + nickname: null + }, + s: number +}; type memberChunk = { diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 0422d500..24c73d08 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -124,12 +124,14 @@ class Localuser{ const user = new User(thing.user, this); user.nickname = thing.nickname; user.relationshipType = thing.type; + this.inrelation.add(user); } this.pingEndpoint(); this.userinfo.updateLocal(); } + inrelation=new Set(); outoffocus(): void{ const servers = document.getElementById("servers") as HTMLDivElement; servers.innerHTML = ""; @@ -365,6 +367,7 @@ class Localuser{ }); await promise; } + relationshipsUpdate=()=>{}; async handleEvent(temp: wsjson){ console.debug(temp); if(temp.s)this.lastSequence = temp.s; @@ -539,6 +542,23 @@ class Localuser{ guild.memberupdate(temp.d) break } + case "RELATIONSHIP_ADD":{ + const user = new User(temp.d.user, this); + user.nickname = null; + user.relationshipType = temp.d.type; + this.inrelation.add(user); + this.relationshipsUpdate(); + break; + } + case "RELATIONSHIP_REMOVE":{ + const user = this.userMap.get(temp.d.id); + if(!user) return; + user.nickname = null; + user.relationshipType = 0; + this.inrelation.delete(user); + this.relationshipsUpdate(); + break; + } default :{ //@ts-ignore console.warn("Unhandled case "+temp.t,temp); diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 9c21cf23..a4bae0c8 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -516,11 +516,27 @@ class HtmlArea implements OptionsElement{ } watchForChange(){} } +/** +* This is a simple wrapper class for Options to make it happy so it can be used outside of Settings. +*/ +class Float{ + options:Options; + /** + * This is a simple wrapper class for Options to make it happy so it can be used outside of Settings. + */ + constructor(name:string, options={ ltr:false, noSubmit:false}){ + this.options=new Options(name,this,options) + } + changed=()=>{}; + generateHTML(){ + return this.options.generateHTML(); + } +} class Options implements OptionsElement{ name: string; haschanged = false; readonly options: OptionsElement[]; - readonly owner: Buttons | Options | Form; + readonly owner: Buttons | Options | Form | Float; readonly ltr: boolean; value!: void; readonly html: WeakMap, WeakRef> = new WeakMap(); @@ -530,7 +546,7 @@ class Options implements OptionsElement{ ); constructor( name: string, - owner: Buttons | Options | Form, + owner: Buttons | Options | Form | Float, { ltr = false, noSubmit=false} = {} ){ this.name = name; @@ -1145,38 +1161,84 @@ class Form implements OptionsElement{ } console.log("middle2"); await Promise.allSettled(promises); - this.preprocessor(build); + try{ + this.preprocessor(build); + }catch(e){ + if(e instanceof FormError){ + const elm = this.options.html.get(e.elem); + if(elm){ + const html = elm.deref(); + if(html){ + this.makeError(html, e.message); + } + } + } + return; + } if(this.fetchURL !== ""){ fetch(this.fetchURL, { method: this.method, body: JSON.stringify(build), headers: this.headers, }) - .then(_=>_.json()) + .then(_=>{ + return _.text() + }).then(_=>{ + if(_==="") return {}; + return JSON.parse(_) + }) .then(json=>{ - if(json.errors && this.errors(json.errors))return; - this.onSubmit(json); + if(json.errors){ + if(this.errors(json)){ + return; + } + } + try{ + this.onSubmit(json); + }catch(e){ + console.error(e); + if(e instanceof FormError){ + const elm = this.options.html.get(e.elem); + if(elm){ + const html = elm.deref(); + if(html){ + this.makeError(html, e.message); + } + } + } + return; + } }); }else{ - this.onSubmit(build); + try{ + this.onSubmit(build); + }catch(e){ + if(e instanceof FormError){ + const elm = this.options.html.get(e.elem); + if(elm){ + const html = elm.deref(); + if(html){ + this.makeError(html, e.message); + } + } + } + return; + } } console.warn("needs to be implemented"); } - errors(errors: { - code: number; - message: string; - errors: { [key: string]: { _errors: { message: string; code: string } } }; - }){ + errors(errors: {code: number; message: string; errors: { [key: string]: { _errors: { message: string; code: string }[] } }}){ if(!(errors instanceof Object)){ return; } - for(const error of Object.keys(errors)){ + for(const error of Object.keys(errors.errors)){ const elm = this.names.get(error); if(elm){ const ref = this.options.html.get(elm); if(ref && ref.deref()){ const html = ref.deref() as HTMLDivElement; - this.makeError(html, errors.errors[error]._errors.message); + const errorMessage=errors.errors[error]._errors[0].message; + this.makeError(html, errorMessage); return true; } } @@ -1253,4 +1315,4 @@ class Settings extends Buttons{ } } -export{ Settings, OptionsElement, Buttons, Options,Form }; +export{ Settings, OptionsElement, Buttons, Options,Form,Float }; diff --git a/src/webpage/style.css b/src/webpage/style.css index fb23e4ce..87cbc96e 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -256,9 +256,15 @@ textarea { } .svg-friends{ mask: url(/icons/friends.svg); - width: 24px !important;!i;!; - height: 24px !important;!i;!; - margin-right: 0 !important;!i;!; + width: 24px !important; + height: 24px !important; + margin-right: 0 !important; +} +.svg-frmessage{ + mask: url(/icons/frmessage.svg); +} +.svg-addfriend{ + mask: url(/icons/addfriend.svg); } .svgicon { display: block; @@ -833,6 +839,9 @@ span.instanceStatus { margin: auto 0 0 8px; font-size: .9em; color: var(--primary-text-soft); + button{ + margin-right:.05in; + } } #channelTopic[hidden] { display: none; @@ -2045,7 +2054,18 @@ fieldset input[type="radio"] { margin: 6px 12px; } } - +.friendcontainer{ + display: flex; + width: 100%; + padding: .2in; + >div{ + background:#00000030; + margin-bottom:.1in; + padding:.06in .1in; + border-radius:.1in; + border: solid 1px var(--black); + } +} .fixedsearch{ position: absolute; background: var(--primary-bg); @@ -2064,6 +2084,35 @@ fieldset input[type="radio"] { } } +.suberror{ + animation: goout 6s forwards; +} +.suberrora{ + background:var(--channel-hover); + border-radius:.1in; + position:absolute; + border:solid var(--primary-text) .02in; + color:color-mix(in hsl,var(--yellow),var(--red)); + font-weight:bold; + opacity:0; + cursor:default; + /* height: .4in; */ + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; + padding: .075in; + box-sizing: border-box; + pointer-events: none; +} +@keyframes goout { + 0%,100%{ + opacity:0; + } + 5%,90%{ + opacity:1; + } +} .friendsbutton{ transition: background-color .2s; background-color: #00000050; @@ -2072,3 +2121,16 @@ fieldset input[type="radio"] { .bigemoji{ width:.6in; } +.friendlyButton{ + padding: .07in; + background: #00000045; + transition:background .2s; + border-radius: 1in; + border: solid 1px var(--black); + width: 24px; + height: 24px; + margin: 0 .05in; +} +.friendlyButton:hover{ + background:black; +} diff --git a/src/webpage/user.ts b/src/webpage/user.ts index e47b7c35..621793f9 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -8,6 +8,7 @@ import{ presencejson, userjson }from"./jsontypes.js"; import { Role } from "./role.js"; import { Search } from "./search.js"; import { I18n } from "./i18n.js"; +import { Direct } from "./direct.js"; class User extends SnowFlake{ owner: Localuser; @@ -15,7 +16,7 @@ class User extends SnowFlake{ avatar!: string | null; username!: string; nickname: string | null = null; - relationshipType: 0 | 1 | 2 | 3 | 4 = 0; + relationshipType: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0; bio!: MarkDown; discriminator!: string; pronouns!: string; @@ -85,31 +86,59 @@ class User extends SnowFlake{ this.setstatus("offline"); } } - + get online(){ + return (this.status)&&(this.status!="offline"); + } setstatus(status: string): void{ this.status = status; } - async getStatus(): Promise{ + getStatus(): string{ return this.status || "offline"; } static contextmenu = new Contextmenu("User Menu"); - + async opendm(){ + for(const dm of (this.localuser.guildids.get("@me") as Direct).channels){ + if(dm.user.id===this.id){ + this.localuser.goToChannel(dm.id); + return; + } + } + await fetch(this.info.api + "/users/@me/channels", { + method: "POST", + body: JSON.stringify({ recipients: [this.id] }), + headers: this.localuser.headers, + }) + .then(res=>res.json()) + .then(json=>{ + this.localuser.goToChannel(json.id); + }); + return; + } + async changeRelationship(type:0|1|2|3|4|5){ + if(type!==0){ + await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type, + }), + }); + }else{ + await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "DELETE", + headers: this.owner.headers + }); + } + this.relationshipType=type; + } static setUpContextMenu(): void{ this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(this: User){ navigator.clipboard.writeText(this.id); }); this.contextmenu.addbutton(()=>I18n.getTranslation("user.message"), function(this: User){ - fetch(this.info.api + "/users/@me/channels", { - method: "POST", - body: JSON.stringify({ recipients: [this.id] }), - headers: this.localuser.headers, - }) - .then(res=>res.json()) - .then(json=>{ - this.localuser.goToChannel(json.id); - }); + this.opendm(); }); this.contextmenu.addbutton( ()=>I18n.getTranslation("user.block"), @@ -133,13 +162,7 @@ class User extends SnowFlake{ } ); this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "PUT", - headers: this.owner.headers, - body: JSON.stringify({ - type: 1, - }), - }); + this.changeRelationship(1); }); this.contextmenu.addbutton( ()=>I18n.getTranslation("user.kick"), @@ -370,15 +393,8 @@ class User extends SnowFlake{ ); } - block(): void{ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "PUT", - headers: this.owner.headers, - body: JSON.stringify({ - type: 2, - }), - }); - this.relationshipType = 2; + async block(){ + await this.changeRelationship(2); const channel = this.localuser.channelfocus; if(channel){ for(const message of channel.messages){ @@ -387,12 +403,8 @@ class User extends SnowFlake{ } } - unblock(): void{ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "DELETE", - headers: this.owner.headers, - }); - this.relationshipType = 0; + async unblock(){ + await this.changeRelationship(0); const channel = this.localuser.channelfocus; if(channel){ for(const message of channel.messages){ diff --git a/translations/en.json b/translations/en.json index 370939b4..e27ef3f2 100644 --- a/translations/en.json +++ b/translations/en.json @@ -229,7 +229,9 @@ "noDelete":"Nevermind", "create":"Create guild", "loadingDiscovery":"Loading...", - "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}" + "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}", + "emptytitle":"Weird spot", + "emptytext":"You're in a weird spot, this guild has no channels" }, "role":{ "displaySettings":"Display settings", @@ -345,11 +347,28 @@ "joinUsing":"Join using invite", "inviteLinkCode":"Invite Link/Code" }, + "friends":{ + "blocked":"Blocked", + "blockedusers":"Blocked Users:", + "addfriend":"Add Friend", + "addfriendpromt":"Add friends by username:", + "notfound":"User not found", + "discnotfound":"Discriminator not found", + "pending":"Pending", + "pending:":"Pending friend requests:", + "all":"All", + "all:":"All friends:", + "online":"Online", + "online:":"Online friends:", + "friendlist":"Friend List", + "friends":"Friends" + }, "replyingTo":"Replying to $1", "DMs":{ "copyId":"Copy DM id", "markRead":"Mark as read", - "close":"Close DM" + "close":"Close DM", + "name":"Dirrect Messages" }, "user":{ "copyId":"Copy user ID", From 258fba7b8c2d70a5d757907a215e4c60ff01d5fc Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 00:38:17 -0600 Subject: [PATCH 0089/1330] typo fix --- translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/en.json b/translations/en.json index e27ef3f2..e1629e4d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -368,7 +368,7 @@ "copyId":"Copy DM id", "markRead":"Mark as read", "close":"Close DM", - "name":"Dirrect Messages" + "name":"Direct Messages" }, "user":{ "copyId":"Copy user ID", From d4d5da9da4aa4fd08a913c5ac843d91ab4009132 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 18:16:01 -0600 Subject: [PATCH 0090/1330] get rid of the dialog class/improved notifications --- src/webpage/channel.ts | 170 ++++++++++++++++-------- src/webpage/dialog.ts | 273 --------------------------------------- src/webpage/disimg.ts | 37 ++++++ src/webpage/embed.ts | 8 +- src/webpage/file.ts | 4 +- src/webpage/guild.ts | 225 ++++++++++---------------------- src/webpage/jsontypes.ts | 7 +- src/webpage/localuser.ts | 160 ++++++++--------------- src/webpage/login.ts | 90 +++++-------- src/webpage/markdown.ts | 47 +++---- src/webpage/member.ts | 60 +++------ src/webpage/message.ts | 43 +++--- src/webpage/settings.ts | 135 ++++++++++++++++--- src/webpage/style.css | 15 ++- translations/en.json | 11 +- 15 files changed, 500 insertions(+), 785 deletions(-) delete mode 100644 src/webpage/dialog.ts create mode 100644 src/webpage/disimg.ts diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 01dab529..b798ed67 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -2,11 +2,10 @@ import{ Message }from"./message.js"; import{ AVoice }from"./audio.js"; import{ Contextmenu }from"./contextmenu.js"; -import{ Dialog }from"./dialog.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; import{ Permissions }from"./permissions.js"; -import{ Settings }from"./settings.js"; +import{ BDialog, Settings }from"./settings.js"; import{ Role, RoleList }from"./role.js"; import{ InfiniteScroller }from"./infiniteScroller.js"; import{ SnowFlake }from"./snowflake.js"; @@ -43,7 +42,7 @@ class Channel extends SnowFlake{ lastpin!: string; move_id?: string; typing!: number; - message_notifications!: number; + message_notifications:number=3; allthewayup!: boolean; static contextmenu = new Contextmenu("channel menu"); replyingto!: Message | null; @@ -53,6 +52,14 @@ class Channel extends SnowFlake{ messages: Map = new Map(); voice?:Voice; bitrate:number=128000; + + muted:boolean=false; + mute_config= {selected_time_window: -1,end_time: 0} + handleUserOverrides(settings:{message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}){ + this.message_notifications=settings.message_notifications; + this.muted=settings.muted; + this.mute_config=settings.mute_config; + } static setupcontextmenu(){ this.contextmenu.addbutton(()=>I18n.getTranslation("channel.copyId"), function(this: Channel){ navigator.clipboard.writeText(this.id); @@ -76,6 +83,12 @@ class Channel extends SnowFlake{ return this.isAdmin(); } ); + this.contextmenu.addbutton( + ()=>I18n.getTranslation("guild.notifications"), + function(){ + this.setnotifcation(); + } + ) this.contextmenu.addbutton( ()=>I18n.getTranslation("channel.makeInvite"), @@ -129,50 +142,21 @@ class Channel extends SnowFlake{ }); }; update(); - new Dialog([ - "vdiv", - ["title", I18n.getTranslation("inviteOptions.title")], - ["text", `to #${this.name} in ${this.guild.properties.name}`], - [ - "select", - "Expire after:", - [ - I18n.getTranslation("inviteOptions.30m"), - I18n.getTranslation("inviteOptions.1h"), - I18n.getTranslation("inviteOptions.6h"), - I18n.getTranslation("inviteOptions.12h"), - I18n.getTranslation("inviteOptions.1d"), - I18n.getTranslation("inviteOptions.7d"), - I18n.getTranslation("inviteOptions.30d"), - I18n.getTranslation("inviteOptions.never"), - ], - function(e: Event){ - expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0,][(e.srcElement as HTMLSelectElement).selectedIndex]; + const inviteOptions=new BDialog("",{noSubmit:true}); + inviteOptions.options.addTitle(I18n.getTranslation("inviteOptions.title")); + inviteOptions.options.addText(I18n.getTranslation("invite.subtext",this.name,this.guild.properties.name)); - update(); - }, - 0, - ], - [ - "select", - "Max uses:", - [ - I18n.getTranslation("inviteOptions.noLimit"), - I18n.getTranslation("inviteOptions.limit","1"), - I18n.getTranslation("inviteOptions.limit","5"), - I18n.getTranslation("inviteOptions.limit","10"), - I18n.getTranslation("inviteOptions.limit","25"), - I18n.getTranslation("inviteOptions.limit","50"), - I18n.getTranslation("inviteOptions.limit","100"), - ], - function(e: Event){ - uses = [0, 1, 5, 10, 25, 50, 100][(e.srcElement as HTMLSelectElement).selectedIndex]; - update(); - }, - 0, - ], - ["html", div], - ]).show(); + inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, + ["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) + ).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];update()}; + + const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) + timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) + inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) + .onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];update()}; + + inviteOptions.options.addHTMLArea(div); + inviteOptions.show(); } generateSettings(){ this.sortPerms(); @@ -938,6 +922,81 @@ class Channel extends SnowFlake{ } } lastmessage: Message | undefined; + setnotifcation(){ + const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) + const options=["all", "onlyMentions", "none","default"].map(e=>I18n.getTranslation("guild."+e,defualt)); + const notiselect=new BDialog(""); + const form=notiselect.options.addForm("",(_,sent:any)=>{ + notiselect.hide(); + console.log(sent); + this.message_notifications = sent.channel_overrides[this.id].message_notifications; + },{ + fetchURL:`${this.info.api}/users/@me/guilds/${this.guild.id}/settings/`, + method:"PATCH", + headers:this.headers + }); + form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ + radio:true, + defaultIndex:this.message_notifications + },[0,1,2,3]); + + form.addPreprocessor((e:any)=>{ + const message_notifications=e.message_notifications; + delete e.message_notifications; + e.channel_overrides={ + [this.id]:{ + message_notifications, + muted:this.muted, + mute_config:this.mute_config, + channel_id:this.id + } + } + }) + /* + let noti = this.message_notifications; + const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) + const options=["all", "onlyMentions", "none","default"].map(e=>I18n.getTranslation("guild."+e,defualt)) + const notiselect = new Dialog([ + "vdiv", + [ + "radio", + I18n.getTranslation("guild.selectnoti"), + options, + function(e: string){ + noti = options.indexOf(e); + }, + noti, + ], + [ + "button", + "", + "submit", + (_: any)=>{ + // + fetch(this.info.api + `/users/@me/guilds/${this.guild.id}/settings/`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + channel_overrides:{ + [this.id]:{ + message_notifications: noti, + muted:false, + mute_config:{ + selected_time_window:0, + end_time:0 + }, + channel_id:this.id + } + } + }), + }).then(()=>notiselect.hide()); + this.message_notifications = noti; + }, + ], + ]); + */ + notiselect.show(); + } async putmessages(){ //TODO swap out with the WS op code if(this.allthewayup){ @@ -1229,16 +1288,17 @@ class Channel extends SnowFlake{ notinumber = null; } notinumber ??= this.guild.message_notifications; + console.warn("info:",notinumber); switch(Number(notinumber)){ - case 0: - return"all"; - case 1: - return"mentions"; - case 2: - return"none"; - case 3: - default: - return"default"; + case 0: + return"all"; + case 1: + return"mentions"; + case 2: + return"none"; + case 3: + default: + return"default"; } } async sendMessage( diff --git a/src/webpage/dialog.ts b/src/webpage/dialog.ts deleted file mode 100644 index fe7a2fdd..00000000 --- a/src/webpage/dialog.ts +++ /dev/null @@ -1,273 +0,0 @@ -type dialogjson = -| ["hdiv", ...dialogjson[]] -| ["vdiv", ...dialogjson[]] -| ["img", string, [number, number] | undefined | ["fit"]] -| ["checkbox", string, boolean, (this: HTMLInputElement, e: Event) => unknown] -| ["button", string, string, (this: HTMLButtonElement, e: Event) => unknown] -| ["mdbox", string, string, (this: HTMLTextAreaElement, e: Event) => unknown] -| ["textbox", string, string, (this: HTMLInputElement, e: Event) => unknown] -| ["fileupload", string, (this: HTMLInputElement, e: Event) => unknown] -| ["text", string] -| ["title", string] -| ["radio", string, string[], (this: unknown, e: string) => unknown, number] -| ["html", HTMLElement] -| ["select", string, string[], (this: HTMLSelectElement, e: Event) => unknown, number] -| ["tabs", [string, dialogjson][]]; -class Dialog{ - layout: dialogjson; - onclose: Function; - onopen: Function; - html: HTMLDivElement; - background!: HTMLDivElement; - constructor( - layout: dialogjson, - onclose = (_: any)=>{}, - onopen = (_: any)=>{} - ){ - this.layout = layout; - this.onclose = onclose; - this.onopen = onopen; - const div = document.createElement("div"); - div.appendChild(this.tohtml(layout)); - this.html = div; - this.html.classList.add("centeritem"); - if(!(layout[0] === "img")){ - this.html.classList.add("nonimagecenter"); - } - } - tohtml(array: dialogjson): HTMLElement{ - switch(array[0]){ - case"img": - const img = document.createElement("img"); - img.src = array[1]; - if(array[2] != undefined){ - if(array[2].length === 2){ - img.width = array[2][0]; - img.height = array[2][1]; - }else if(array[2][0] === "fit"){ - img.classList.add("imgfit"); - } - } - return img; - case"hdiv": - const hdiv = document.createElement("div"); - hdiv.classList.add("flexltr"); - - for(const thing of array){ - if(thing === "hdiv"){ - continue; - } - hdiv.appendChild(this.tohtml(thing)); - } - return hdiv; - case"vdiv": - const vdiv = document.createElement("div"); - vdiv.classList.add("flexttb"); - for(const thing of array){ - if(thing === "vdiv"){ - continue; - } - vdiv.appendChild(this.tohtml(thing)); - } - return vdiv; - case"checkbox": { - const div = document.createElement("div"); - const checkbox = document.createElement("input"); - div.appendChild(checkbox); - const label = document.createElement("span"); - checkbox.checked = array[2]; - label.textContent = array[1]; - div.appendChild(label); - checkbox.addEventListener("change", array[3]); - checkbox.type = "checkbox"; - return div; - } - case"button": { - const div = document.createElement("div"); - const input = document.createElement("button"); - - const label = document.createElement("span"); - input.textContent = array[2]; - label.textContent = array[1]; - div.appendChild(label); - div.appendChild(input); - input.addEventListener("click", array[3]); - return div; - } - case"mdbox": { - const div = document.createElement("div"); - const input = document.createElement("textarea"); - input.value = array[2]; - const label = document.createElement("span"); - label.textContent = array[1]; - input.addEventListener("input", array[3]); - div.appendChild(label); - div.appendChild(document.createElement("br")); - div.appendChild(input); - return div; - } - case"textbox": { - const div = document.createElement("div"); - const input = document.createElement("input"); - input.value = array[2]; - input.type = "text"; - const label = document.createElement("span"); - label.textContent = array[1]; - console.log(array[3]); - input.addEventListener("input", array[3]); - div.appendChild(label); - div.appendChild(input); - return div; - } - case"fileupload": { - const div = document.createElement("div"); - const input = document.createElement("input"); - input.type = "file"; - const label = document.createElement("span"); - label.textContent = array[1]; - div.appendChild(label); - div.appendChild(input); - input.addEventListener("change", array[2]); - console.log(array); - return div; - } - case"text": { - const span = document.createElement("span"); - span.textContent = array[1]; - return span; - } - case"title": { - const span = document.createElement("span"); - span.classList.add("title"); - span.textContent = array[1]; - return span; - } - case"radio": { - const div = document.createElement("div"); - const fieldset = document.createElement("fieldset"); - fieldset.addEventListener("change", ()=>{ - let i = -1; - for(const thing of Array.from(fieldset.children)){ - i++; - if(i === 0){ - continue; - } - const checkbox = thing.children[0].children[0] as HTMLInputElement; - if(checkbox.checked){ - array[3](checkbox.value); - } - } - }); - const legend = document.createElement("legend"); - legend.textContent = array[1]; - fieldset.appendChild(legend); - let i = 0; - for(const thing of array[2]){ - const div = document.createElement("div"); - const input = document.createElement("input"); - input.classList.add("radio"); - input.type = "radio"; - input.name = array[1]; - input.value = thing; - if(i === array[4]){ - input.checked = true; - } - const label = document.createElement("label"); - - label.appendChild(input); - const span = document.createElement("span"); - span.textContent = thing; - label.appendChild(span); - div.appendChild(label); - fieldset.appendChild(div); - i++; - } - div.appendChild(fieldset); - return div; - } - case"html": - return array[1]; - - case"select": { - const div = document.createElement("div"); - const label = document.createElement("label"); - const selectSpan = document.createElement("span"); - selectSpan.classList.add("selectspan"); - const select = document.createElement("select"); - const selectArrow = document.createElement("span"); - selectArrow.classList.add("svgicon","svg-category","selectarrow"); - - label.textContent = array[1]; - selectSpan.append(select); - selectSpan.append(selectArrow); - div.append(label); - div.appendChild(selectSpan); - for(const thing of array[2]){ - const option = document.createElement("option"); - option.textContent = thing; - select.appendChild(option); - } - select.selectedIndex = array[4]; - select.addEventListener("change", array[3]); - return div; - } - case"tabs": { - const table = document.createElement("div"); - table.classList.add("flexttb"); - const tabs = document.createElement("div"); - tabs.classList.add("flexltr"); - tabs.classList.add("tabbed-head"); - table.appendChild(tabs); - const content = document.createElement("div"); - content.classList.add("tabbed-content"); - table.appendChild(content); - - let shown: HTMLElement | undefined; - for(const thing of array[1]){ - const button = document.createElement("button"); - button.textContent = thing[0]; - tabs.appendChild(button); - - const html = this.tohtml(thing[1]); - content.append(html); - if(!shown){ - shown = html; - }else{ - html.style.display = "none"; - } - button.addEventListener("click", _=>{ - if(shown){ - shown.style.display = "none"; - } - html.style.display = ""; - shown = html; - }); - } - return table; - } - default: - console.error( - "can't find element:" + array[0], - " full element:", - array - ); - return document.createElement("span"); - } - } - show(){ - this.onopen(); - console.log("fullscreen"); - this.background = document.createElement("div"); - this.background.classList.add("background"); - document.body.appendChild(this.background); - document.body.appendChild(this.html); - this.background.onclick = _=>{ - this.hide(); - }; - } - hide(){ - document.body.removeChild(this.background); - document.body.removeChild(this.html); - } -} -export{ Dialog }; diff --git a/src/webpage/disimg.ts b/src/webpage/disimg.ts new file mode 100644 index 00000000..a1d2a1d4 --- /dev/null +++ b/src/webpage/disimg.ts @@ -0,0 +1,37 @@ +class ImagesDisplay{ + images:string[]; + index=0; + constructor(srcs:string[],index=0){ + this.images=srcs; + this.index=index; + } + weakbg=new WeakRef(document.createElement("div")); + get background():HTMLElement|undefined{ + return this.weakbg.deref(); + } + set background(e:HTMLElement){ + this.weakbg=new WeakRef(e); + } + makeHTML():HTMLElement{ + //TODO this should be able to display more than one image at a time lol + const image= document.createElement("img"); + image.src=this.images[this.index]; + image.classList.add("imgfit","centeritem"); + return image; + } + show(){ + this.background = document.createElement("div"); + this.background.classList.add("background"); + this.background.appendChild(this.makeHTML()); + this.background.onclick = _=>{ + this.hide(); + }; + document.body.append(this.background); + } + hide(){ + if(this.background){ + this.background.remove(); + } + } +} +export{ImagesDisplay} diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index d2f8fe92..e84105d2 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -1,10 +1,10 @@ -import{ Dialog }from"./dialog.js"; import{ Message }from"./message.js"; import{ MarkDown }from"./markdown.js"; import{ embedjson, invitejson }from"./jsontypes.js"; import{ getapiurls, getInstances }from"./login.js"; import{ Guild }from"./guild.js"; import { I18n } from "./i18n.js"; +import { ImagesDisplay } from "./disimg.js"; class Embed{ type: string; @@ -178,7 +178,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; const img = document.createElement("img"); img.classList.add("messageimg"); img.onclick = function(){ - const full = new Dialog(["img", img.src, ["fit"]]); + const full = new ImagesDisplay([img.src]); full.show(); }; img.src = this.json.thumbnail.proxy_url; @@ -214,7 +214,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; if(this.json.thumbnail){ img.classList.add("embedimg"); img.onclick = function(){ - const full = new Dialog(["img", img.src, ["fit"]]); + const full = new ImagesDisplay([img.src]); full.show(); }; img.src = this.json.thumbnail.proxy_url; @@ -392,7 +392,7 @@ guild as invitejson["guild"] & { info: { cdn: string } } }; }else{ img.onclick = async ()=>{ - const full = new Dialog(["img", img.src, ["fit"]]); + const full = new ImagesDisplay([img.src]); full.show(); }; } diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 3a67853a..25a6b34d 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -1,6 +1,6 @@ import{ Message }from"./message.js"; -import{ Dialog }from"./dialog.js"; import{ filejson }from"./jsontypes.js"; +import { ImagesDisplay } from "./disimg.js"; class File{ owner: Message | null; @@ -40,7 +40,7 @@ class File{ img.classList.add("messageimg"); div.classList.add("messageimgdiv"); img.onclick = function(){ - const full = new Dialog(["img", img.src, ["fit"]]); + const full = new ImagesDisplay([img.src]); full.show(); }; img.src = src; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 79e7b7d3..48118ef2 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -2,9 +2,8 @@ import{ Channel }from"./channel.js"; import{ Localuser }from"./localuser.js"; import{ Contextmenu }from"./contextmenu.js"; import{ Role, RoleList }from"./role.js"; -import{ Dialog }from"./dialog.js"; import{ Member }from"./member.js"; -import{ Settings }from"./settings.js"; +import{ BDialog, Settings }from"./settings.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson,}from"./jsontypes.js"; @@ -238,7 +237,7 @@ class Guild extends SnowFlake{ this.localuser.perminfo.guilds[this.id] = e; } notisetting(settings: { - channel_overrides?: unknown[]; + channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; message_notifications: any; flags?: number; hide_muted_channels?: boolean; @@ -253,66 +252,42 @@ class Guild extends SnowFlake{ guild_id?: string; }){ this.message_notifications = settings.message_notifications; + for(const override of settings.channel_overrides){ + const channel=this.localuser.channelids.get(override.channel_id); + if(!channel) continue; + channel.handleUserOverrides(override); + } } setnotifcation(){ - let noti = this.message_notifications; - const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e)) - const notiselect = new Dialog([ - "vdiv", - [ - "radio", - I18n.getTranslation("guild.selectnoti"), - options, - function(e: string){ - noti = options.indexOf(e); - }, - noti, - ], - [ - "button", - "", - "submit", - (_: any)=>{ - // - fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - message_notifications: noti, - }), - }); - this.message_notifications = noti; - }, - ], - ]); + + const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e)); + const notiselect=new BDialog(""); + const form=notiselect.options.addForm("",(_,sent:any)=>{ + notiselect.hide(); + this.message_notifications = sent.message_notifications; + },{ + fetchURL:`${this.info.api}/users/@me/guilds/${this.id}/settings/`, + method:"PATCH", + headers:this.headers + }); + form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ + radio:true, + defaultIndex:this.message_notifications + },[0,1,2]); notiselect.show(); } confirmleave(){ - const full = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("guild.confirmLeave")], - [ - "hdiv", - [ - "button", - "", - I18n.getTranslation("guild.yesLeave"), - (_: any)=>{ - this.leave().then(_=>{ - full.hide(); - }); - }, - ], - [ - "button", - "", - I18n.getTranslation("guild.noLeave"), - (_: any)=>{ - full.hide(); - }, - ], - ], - ]); + const full = new BDialog(""); + full.options.addTitle(I18n.getTranslation("guild.confirmLeave")) + const options=full.options.addOptions("",{ltr:true}); + options.addButtonInput("",I18n.getTranslation("guild.yesLeave"),()=>{ + this.leave().then(_=>{ + full.hide(); + }); + }); + options.addButtonInput("",I18n.getTranslation("guild.noLeave"),()=>{ + full.hide(); + }); full.show(); } async leave(){ @@ -458,46 +433,26 @@ class Guild extends SnowFlake{ } confirmDelete(){ let confirmname = ""; - const full = new Dialog([ - "vdiv", - [ - "title", - I18n.getTranslation("guild.confirmDelete",this.properties.name) - ], - [ - "textbox", - I18n.getTranslation("guild.serverName"), - "", - function(this: HTMLInputElement){ - confirmname = this.value; - }, - ], - [ - "hdiv", - [ - "button", - "", - I18n.getTranslation("guild.yesDelete"), - (_: any)=>{ - console.log(confirmname); - if(confirmname !== this.properties.name){ - return; - } - this.delete().then(_=>{ - full.hide(); - }); - }, - ], - [ - "button", - "", - I18n.getTranslation("guild.noDelete"), - (_: any)=>{ - full.hide(); - }, - ], - ], - ]); + + const full = new BDialog(""); + full.options.addTitle(I18n.getTranslation("guild.confirmDelete",this.properties.name)); + full.options.addTextInput(I18n.getTranslation("guild.serverName"),()=>{}).onchange=(e)=>confirmname=e; + + const options=full.options.addOptions("",{ltr:true}); + options.addButtonInput("",I18n.getTranslation("guild.yesDelete"),()=>{ + if(confirmname !== this.properties.name){ + //TODO maybe some sort of form error? idk + alert("names don't match"); + return; + } + this.delete().then(_=>{ + full.hide(); + }); + }); + + options.addButtonInput("",I18n.getTranslation("guild.noDelete"),()=>{ + full.hide(); + }); full.show(); } async delete(){ @@ -677,67 +632,27 @@ class Guild extends SnowFlake{ return thischannel; } createchannels(func = this.createChannel){ - let name = ""; - let category = 0; - const options=["voice", "text", "announcement"].map(e=>I18n.getTranslation("channel."+e)); - const numbers=[2,0,5] - const channelselect = new Dialog([ - "vdiv", - [ - "radio", - I18n.getTranslation("channel.selectType"), - options, - function(radio: string){ - console.log(radio); - category = numbers[options.indexOf(radio)] || 0; - }, - 1, - ], - [ - "textbox", - I18n.getTranslation("channel.selectName"), - "", - function(this: HTMLInputElement){ - name = this.value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit"), - ()=>{ - console.log(name, category); - func.bind(this)(name, category); - channelselect.hide(); - }, - ], - ]); + const options=["text", "announcement","voice"].map(e=>I18n.getTranslation("channel."+e)); + + const channelselect=new BDialog(""); + const form=channelselect.options.addForm("",(e:any)=>{ + func(e.name,e.type); + channelselect.hide(); + }); + + form.addSelect(I18n.getTranslation("channel.selectType"),"type",options,{radio:true},[0,5,2]); + form.addTextInput(I18n.getTranslation("channel.selectName"),"name"); channelselect.show(); } createcategory(){ - let name = ""; const category = 4; - const channelselect = new Dialog([ - "vdiv", - [ - "textbox", - I18n.getTranslation("channel.selectCatName"), - "", - function(this: HTMLInputElement){ - name = this.value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit"), - function(this:Guild){ - console.log(name, category); - this.createChannel(name, category); - channelselect.hide(); - }.bind(this), - ], - ]); + const channelselect=new BDialog(""); + const options=channelselect.options; + const form=options.addForm("",(e:any)=>{ + this.createChannel(e.name, category); + channelselect.hide(); + }); + form.addTextInput(I18n.getTranslation("channel.selectCatName"),"name"); channelselect.show(); } delChannel(json: channeljson){ diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index ae6134ac..7a3d7a83 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -63,7 +63,7 @@ type readyjson = { }; user_guild_settings: { entries: { - channel_overrides: unknown[]; //will have to find example + channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; message_notifications: number; flags: number; hide_muted_channels: boolean; @@ -523,6 +523,11 @@ roleCreate | { nickname: null }, s: number +}|{ + op: 0, + t: "PRESENCE_UPDATE", + d: presencejson, + s:number }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 24c73d08..79bb0093 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -3,11 +3,10 @@ import{ Channel }from"./channel.js"; 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, 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"; +import{ BDialog, Form, FormError, Options, Settings }from"./settings.js"; import{ getTextNodeAtPosition, MarkDown }from"./markdown.js"; import { Bot } from "./bot.js"; import { Role } from "./role.js"; @@ -26,8 +25,6 @@ class Localuser{ initialized!: boolean; info!: Specialuser["serverurls"]; headers!: { "Content-type": string; Authorization: string }; - userConnections!: Dialog; - devPortal!: Dialog; ready!: readyjson; guilds!: Guild[]; guildids: Map = new Map(); @@ -559,6 +556,12 @@ class Localuser{ this.relationshipsUpdate(); break; } + case "PRESENCE_UPDATE":{ + if(temp.d.user){ + this.presences.set(temp.d.user.id, temp.d); + } + break; + } default :{ //@ts-ignore console.warn("Unhandled case "+temp.t,temp); @@ -888,99 +891,44 @@ class Localuser{ this.unreads(); } createGuild(){ - let inviteurl = ""; - const error = document.createElement("span"); - const fields: { name: string; icon: string | null } = { - name: "", - icon: null, - }; - const full = new Dialog([ - "tabs", - [ - [ - I18n.getTranslation("invite.joinUsing"), - [ - "vdiv", - [ - "textbox", - I18n.getTranslation("invite.inviteLinkCode"), - "", - function(this: HTMLInputElement){ - inviteurl = this.value; - }, - ], - ["html", error], - [ - "button", - "", - I18n.getTranslation("submit"), - (_: any)=>{ - let parsed = ""; - if(inviteurl.includes("/")){ - parsed = - inviteurl.split("/")[inviteurl.split("/").length - 1]; - }else{ - parsed = inviteurl; - } - fetch(this.info.api + "/invites/" + parsed, { - method: "POST", - headers: this.headers, - }) - .then(r=>r.json()) - .then(_=>{ - if(_.message){ - error.textContent = _.message; - } - }); - }, - ], - ], - ], - [ - I18n.getTranslation("guild.create"), - [ - "vdiv", - ["title", I18n.getTranslation("guild.create")], - [ - "fileupload", - I18n.getTranslation("guild.icon:"), - function(event: Event){ - const target = event.target as HTMLInputElement; - if(!target.files)return; - const reader = new FileReader(); - reader.readAsDataURL(target.files[0]); - reader.onload = ()=>{ - fields.icon = reader.result as string; - }; - }, - ], - [ - "textbox", - I18n.getTranslation("guild.name:"), - "", - function(this: HTMLInputElement, event: Event){ - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit"), - ()=>{ - this.makeGuild(fields).then(_=>{ - if(_.message){ - alert(_.errors.name._errors[0].message); - }else{ - full.hide(); - } - }); - }, - ], - ], - ], - ], - ]); + + const full=new BDialog(""); + const buttons=full.options.addButtons("",{top:true}); + const viacode=buttons.add(I18n.getTranslation("invite.joinUsing")); + { + const form=viacode.addForm("",async (e: any)=>{ + let parsed = ""; + if(e.code.includes("/")){ + parsed = e.code.split("/")[e.code.split("/").length - 1]; + }else{ + parsed = e.code; + } + const json=await (await fetch(this.info.api + "/invites/" + parsed, { + method: "POST", + headers: this.headers, + })).json() + if(json.message){ + throw new FormError(text,json.message); + } + full.hide(); + }); + const text=form.addTextInput(I18n.getTranslation("invite.inviteLinkCode"),"code"); + } + const guildcreate=buttons.add(I18n.getTranslation("guild.create")); + { + const form=guildcreate.addForm("",(fields:any)=>{ + this.makeGuild(fields).then(_=>{ + if(_.message){ + alert(_.errors.name._errors[0].message); + }else{ + full.hide(); + } + }); + }); + form.addFileInput(I18n.getTranslation("guild.icon:"),"icon",{files:"one"}); + form.addTextInput(I18n.getTranslation("guild.name:"),"name",{required:true}); + + } full.show(); } async makeGuild(fields: { name: string; icon: string | null }){ @@ -996,7 +944,8 @@ class Localuser{ const content = document.createElement("div"); content.classList.add("flexttb","guildy"); content.textContent = I18n.getTranslation("guild.loadingDiscovery"); - const full = new Dialog(["html", content]); + const full = new BDialog(""); + full.options.addHTMLArea(content); full.show(); const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", { @@ -2147,15 +2096,12 @@ class Localuser{ headers: this.headers, }); const json = await res.json(); - - const dialog = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("instanceStats.name",this.instancePing.name) ], - ["text", I18n.getTranslation("instanceStats.users",json.counts.user)], - ["text", I18n.getTranslation("instanceStats.servers",json.counts.guild)], - ["text", I18n.getTranslation("instanceStats.messages",json.counts.message)], - ["text", I18n.getTranslation("instanceStats.members",json.counts.members)], - ]); + const dialog = new BDialog(""); + dialog.options.addTitle(I18n.getTranslation("instanceStats.name",this.instancePing.name)); + dialog.options.addText(I18n.getTranslation("instanceStats.users",json.counts.user)); + dialog.options.addText(I18n.getTranslation("instanceStats.servers",json.counts.guild)); + dialog.options.addText(I18n.getTranslation("instanceStats.messages",json.counts.message)); + dialog.options.addText(I18n.getTranslation("instanceStats.members",json.counts.members)); dialog.show(); } } diff --git a/src/webpage/login.ts b/src/webpage/login.ts index b79d8196..3e578a27 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,5 +1,5 @@ -import{ Dialog }from"./dialog.js"; import { I18n } from "./i18n.js"; +import { BDialog, FormError } from "./settings.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); @@ -511,65 +511,43 @@ async function login(username: string, password: string, captcha: string){ capty.setAttribute("data-sitekey", response.captcha_sitekey); const script = document.createElement("script"); script.src = "https://js.hcaptcha.com/1/api.js"; - capt!.append(script); - capt!.append(capty); + capt!.append(script); + capt!.append(capty); } }else{ console.log(response); if(response.ticket){ - let onetimecode = ""; - new Dialog([ - "vdiv", - ["title", I18n.getTranslation("2faCode")], - [ - "textbox", - "", - "", - function(this: HTMLInputElement){ - // eslint-disable-next-line no-invalid-this - onetimecode = this.value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit"), - function(){ - fetch(api + "/auth/mfa/totp", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - code: onetimecode, - ticket: response.ticket, - }), - }) - .then(r=>r.json()) - .then(res=>{ - if(res.message){ - alert(res.message); - }else{ - console.warn(res); - if(!res.token)return; - adduser({ - serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string), - email: username, - token: res.token, - }).username = username; - const redir = new URLSearchParams( - window.location.search - ).get("goback"); - if(redir){ - window.location.href = redir; - }else{ - window.location.href = "/channels/@me"; - } - } - }); - }, - ], - ]).show(); + const better=new BDialog(""); + const form=better.options.addForm("",(res:any)=>{ + if(res.message){ + throw new FormError(ti,res.message); + }else{ + console.warn(res); + if(!res.token)return; + adduser({ + serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string), + email: username, + token: res.token, + }).username = username; + const redir = new URLSearchParams( + window.location.search + ).get("goback"); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + } + },{ + fetchURL:api + "/auth/mfa/totp", + method:"POST", + headers:{ + "Content-Type": "application/json", + } + }); + form.addTitle(I18n.getTranslation("2faCode")); + const ti=form.addTextInput("","code"); + better.show() }else{ console.warn(response); if(!response.token)return; diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 13f8167e..6bddf492 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -1,10 +1,10 @@ import{ Channel }from"./channel.js"; -import{ Dialog }from"./dialog.js"; import{ Emoji }from"./emoji.js"; import{ Guild }from"./guild.js"; import { I18n } from "./i18n.js"; import{ Localuser }from"./localuser.js"; import{ Member }from"./member.js"; +import { BDialog } from "./settings.js"; class MarkDown{ txt: string[]; @@ -781,37 +781,20 @@ txt[j + 1] === undefined) if(this.trustedDomains.has(Url.host)){ open(); }else{ - const full: Dialog = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("leaving")], - [ - "text", - I18n.getTranslation("goingToURL",Url.host) - ], - [ - "hdiv", - ["button", "", I18n.getTranslation("nevermind"), (_: any)=>full.hide()], - [ - "button", - "", - I18n.getTranslation("goThere"), - (_: any)=>{ - open(); - full.hide(); - }, - ], - [ - "button", - "", - I18n.getTranslation("goThereTrust"), - (_: any)=>{ - open(); - full.hide(); - this.trustedDomains.add(Url.host); - }, - ], - ], - ]); + const full=new BDialog(""); + full.options.addTitle(I18n.getTranslation("leaving")); + full.options.addText(I18n.getTranslation("goingToURL",Url.host)); + const options=full.options.addOptions("",{ltr:true}); + options.addButtonInput("",I18n.getTranslation("nevermind"),()=>full.hide()); + options.addButtonInput("",I18n.getTranslation("goThere"),()=>{ + open(); + full.hide(); + }); + options.addButtonInput("",I18n.getTranslation("goThereTrust"),()=>{ + open(); + full.hide(); + this.trustedDomains.add(Url.host); + }); full.show(); } }; diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 3de6ed88..47ccbaae 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -3,8 +3,8 @@ import{ Role }from"./role.js"; import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, presencejson }from"./jsontypes.js"; -import{ Dialog }from"./dialog.js"; import { I18n } from "./i18n.js"; +import { BDialog } from "./settings.js"; class Member extends SnowFlake{ static already = {}; @@ -229,28 +229,13 @@ class Member extends SnowFlake{ return this.nick || this.user.username; } kick(){ - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("member.kick",this.name,this.guild.properties.name)], - [ - "textbox", - I18n.getTranslation("member.reason:"), - "", - function(e: Event){ - reason = (e.target as HTMLInputElement).value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit"), - ()=>{ - this.kickAPI(reason); - menu.hide(); - }, - ], - ]); + const menu = new BDialog(""); + const form=menu.options.addForm("",((e:any)=>{ + this.kickAPI(e.reason); + menu.hide(); + })); + form.addTitle(I18n.getTranslation("member.kick",this.name,this.guild.properties.name)); + form.addTextInput(I18n.getTranslation("member.reason:"),"reason"); menu.show(); } kickAPI(reason: string){ @@ -262,28 +247,13 @@ class Member extends SnowFlake{ }); } ban(){ - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("member.ban",this.name,this.guild.properties.name)], - [ - "textbox", - I18n.getTranslation("member.reason:",this.name,this.guild.properties.name), - "", - function(e: Event){ - reason = (e.target as HTMLInputElement).value; - }, - ], - [ - "button", - "", - I18n.getTranslation("submit",this.name,this.guild.properties.name), - ()=>{ - this.banAPI(reason); - menu.hide(); - }, - ], - ]); + const menu = new BDialog(""); + const form=menu.options.addForm("",((e:any)=>{ + this.banAPI(e.reason); + menu.hide(); + })); + form.addTitle(I18n.getTranslation("member.ban",this.name,this.guild.properties.name)); + form.addTextInput(I18n.getTranslation("member.reason:"),"reason"); menu.show(); } addRole(role:Role){ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index cfac8b3d..d8ba76f3 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -10,10 +10,10 @@ import{ File }from"./file.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, messagejson }from"./jsontypes.js"; import{ Emoji }from"./emoji.js"; -import{ Dialog }from"./dialog.js"; import{ mobile }from"./login.js"; import { I18n } from "./i18n.js"; import { Hover } from "./hover.js"; +import { BDialog } from "./settings.js"; class Message extends SnowFlake{ static contextmenu = new Contextmenu("message menu"); @@ -87,7 +87,7 @@ class Message extends SnowFlake{ Message.contextmenu.addbutton( ()=>I18n.getTranslation("message.delete"), function(this: Message){ - this.delete(); + this.confirmDelete(); }, null, function(){ @@ -674,31 +674,7 @@ class Message extends SnowFlake{ this.delete(); return; } - const diaolog = new Dialog([ - "vdiv", - ["title", I18n.getTranslation("deleteConfirm")], - [ - "hdiv", - [ - "button", - "", - I18n.getTranslation("yes"), - ()=>{ - this.delete(); - diaolog.hide(); - }, - ], - [ - "button", - "", - I18n.getTranslation("no"), - ()=>{ - diaolog.hide(); - }, - ], - ] - ]); - diaolog.show(); + this.confirmDelete(); }; } if(buttons.childNodes.length !== 0){ @@ -714,6 +690,19 @@ class Message extends SnowFlake{ }; } } + confirmDelete(){ + const diaolog=new BDialog(""); + diaolog.options.addTitle(I18n.getTranslation("deleteConfirm")); + const options=diaolog.options.addOptions("",{ltr:true}); + options.addButtonInput("",I18n.getTranslation("yes"),()=>{ + this.delete(); + diaolog.hide(); + }); + options.addButtonInput("",I18n.getTranslation("no"),()=>{ + diaolog.hide(); + }) + diaolog.show(); + } updateReactions(){ const reactdiv = this.reactdiv.deref(); if(!reactdiv)return; diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index a4bae0c8..e99d6656 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -14,7 +14,9 @@ class Buttons implements OptionsElement{ buttonList!: HTMLDivElement; warndiv!: HTMLElement; value: unknown; - constructor(name: string){ + top=false; + constructor(name: string,{top=false}={}){ + this.top=top; this.buttons = []; this.name = name; } @@ -28,7 +30,7 @@ class Buttons implements OptionsElement{ generateHTML(){ const buttonList = document.createElement("div"); buttonList.classList.add("Buttons"); - buttonList.classList.add("flexltr"); + buttonList.classList.add(this.top?"flexttb":"flexltr"); this.buttonList = buttonList; const htmlarea = document.createElement("div"); htmlarea.classList.add("flexgrow"); @@ -43,6 +45,9 @@ class Buttons implements OptionsElement{ generateButtons(optionsArea:HTMLElement){ const buttonTable = document.createElement("div"); buttonTable.classList.add("settingbuttons"); + if(this.top){ + buttonTable.classList.add("flexltr"); + } for(const thing of this.buttons){ const button = document.createElement("button"); button.classList.add("SettingsButton"); @@ -328,6 +333,7 @@ class SelectInput implements OptionsElement{ options: string[]; index: number; select!: WeakRef; + radio:boolean; get value(){ return this.index; } @@ -336,15 +342,61 @@ class SelectInput implements OptionsElement{ onSubmit: (str: number) => void, options: string[], owner: Options, - { defaultIndex = 0 } = {} + { defaultIndex = 0,radio=false } = {} ){ this.label = label; this.index = defaultIndex; this.owner = owner; this.onSubmit = onSubmit; this.options = options; + this.radio=radio; } generateHTML(): HTMLDivElement{ + if(this.radio){ + const map=new WeakMap(); + const div = document.createElement("div"); + const fieldset = document.createElement("fieldset"); + fieldset.addEventListener("change", ()=>{ + let i = -1; + for(const thing of Array.from(fieldset.children)){ + i++; + if(i === 0){ + continue; + } + const checkbox = thing.children[0].children[0] as HTMLInputElement; + if(checkbox.checked){ + this.onChange(map.get(checkbox)); + } + } + }); + const legend = document.createElement("legend"); + legend.textContent = this.label; + fieldset.appendChild(legend); + let i = 0; + for(const thing of this.options){ + const div = document.createElement("div"); + const input = document.createElement("input"); + input.classList.add("radio"); + input.type = "radio"; + input.name = this.label; + input.value = thing; + map.set(input,i); + if(i === this.index){ + input.checked = true; + } + const label = document.createElement("label"); + + label.appendChild(input); + const span = document.createElement("span"); + span.textContent = thing; + label.appendChild(span); + div.appendChild(label); + fieldset.appendChild(div); + i++; + } + div.appendChild(fieldset); + return div; + } const div = document.createElement("div"); const span = document.createElement("span"); span.textContent = this.label; @@ -353,7 +405,7 @@ class SelectInput implements OptionsElement{ selectSpan.classList.add("selectspan"); const select = document.createElement("select"); - select.onchange = this.onChange.bind(this); + select.onchange = this.onChange.bind(this,-1); for(const thing of this.options){ const option = document.createElement("option"); option.textContent = thing; @@ -368,8 +420,13 @@ class SelectInput implements OptionsElement{ div.append(selectSpan); return div; } - private onChange(){ + private onChange(index=-1){ this.owner.changed(); + if(index!==-1){ + this.onchange(index); + this.index = index; + return; + } const select = this.select.deref(); if(select){ const value = select.selectedIndex; @@ -524,7 +581,7 @@ class Float{ /** * This is a simple wrapper class for Options to make it happy so it can be used outside of Settings. */ - constructor(name:string, options={ ltr:false, noSubmit:false}){ + constructor(name:string, options={ ltr:false, noSubmit:true}){ this.options=new Options(name,this,options) } changed=()=>{}; @@ -532,6 +589,38 @@ class Float{ return this.options.generateHTML(); } } +class BDialog{ + float:Float; + get options(){ + return this.float.options; + } + background=new WeakRef(document.createElement("div")); + constructor(name:string, { ltr=false, noSubmit=true}={}){ + this.float=new Float(name,{ltr,noSubmit}); + } + show(){ + const background = document.createElement("div"); + background.classList.add("background"); + const center=this.float.generateHTML(); + center.classList.add("centeritem","nonimagecenter"); + center.classList.remove("titlediv"); + background.append(center); + center.onclick=e=>{ + e.stopImmediatePropagation(); + } + document.body.append(background); + this.background=new WeakRef(background); + background.onclick = _=>{ + background.remove(); + }; + } + hide(){ + const background=this.background.deref(); + if(!background) return; + background.remove(); + } +} +export{BDialog}; class Options implements OptionsElement{ name: string; haschanged = false; @@ -571,6 +660,12 @@ class Options implements OptionsElement{ this.generate(options); return options; } + addButtons(name: string, { top = false } = {}){ + const buttons = new Buttons(name, { top }); + this.options.push(buttons); + this.generate(buttons); + return buttons; + } subOptions: Options | Form | undefined; genTop(){ const container = this.container.deref(); @@ -596,7 +691,7 @@ class Options implements OptionsElement{ } addSubForm( name: string, - onSubmit: (arg1: object) => void, + onSubmit: (arg1: object,sent:object) => void, { ltr = false, submitText = "Submit", @@ -626,10 +721,10 @@ class Options implements OptionsElement{ label: string, onSubmit: (str: number) => void, selections: string[], - { defaultIndex = 0 } = {} + { defaultIndex = 0,radio=false } = {} ){ const select = new SelectInput(label, onSubmit, selections, this, { - defaultIndex, + defaultIndex,radio }); this.options.push(select); this.generate(select); @@ -717,7 +812,7 @@ class Options implements OptionsElement{ } addForm( name: string, - onSubmit: (arg1: object) => void, + onSubmit: (arg1: object,sent:object) => void, { ltr = false, submitText = "Submit", @@ -901,7 +996,7 @@ class Form implements OptionsElement{ constructor( name: string, owner: Options, - onSubmit: (arg1: object) => void, + onSubmit: (arg1: object,sent:object) => void, { ltr = false, submitText = I18n.getTranslation("submit"), @@ -934,7 +1029,7 @@ class Form implements OptionsElement{ } addSubForm( name: string, - onSubmit: (arg1: object) => void, + onSubmit: (arg1: object,sent:object) => void, { ltr = false, submitText = I18n.getTranslation("submit"), @@ -956,16 +1051,16 @@ class Form implements OptionsElement{ (this.button.deref() as HTMLElement).hidden=false; } } - selectMap=new WeakMap(); + selectMap=new WeakMap(); addSelect( label: string, formName: string, selections: string[], - { defaultIndex = 0, required = false}={}, - correct:string[]=selections + { defaultIndex = 0, required = false,radio=false}={}, + correct:(string|number)[]=selections ){ const select = this.options.addSelect(label, _=>{}, selections, { - defaultIndex, + defaultIndex,radio }); this.selectMap.set(select,correct); this.names.set(formName, select); @@ -1084,7 +1179,7 @@ class Form implements OptionsElement{ } return div; } - onSubmit: (arg1: object) => void; + onSubmit: ((arg1: object,sent:object) => void )|((arg1: object,sent:object) => Promise ); watchForChange(func: (arg1: object) => void){ this.onSubmit = func; } @@ -1187,14 +1282,14 @@ class Form implements OptionsElement{ if(_==="") return {}; return JSON.parse(_) }) - .then(json=>{ + .then(async json=>{ if(json.errors){ if(this.errors(json)){ return; } } try{ - this.onSubmit(json); + await this.onSubmit(json,build); }catch(e){ console.error(e); if(e instanceof FormError){ @@ -1211,7 +1306,7 @@ class Form implements OptionsElement{ }); }else{ try{ - this.onSubmit(build); + await this.onSubmit(build,build); }catch(e){ if(e instanceof FormError){ const elm = this.options.html.get(e.elem); diff --git a/src/webpage/style.css b/src/webpage/style.css index 87cbc96e..cc156621 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1614,12 +1614,12 @@ img.bigembedimg { gap: 8px; overflow-y: auto; } -.nonimagecenter .flexttb, .nonimagecenter .flexltr { +.nonimagecenter & .flexttb, .nonimagecenter & .flexltr { flex: 1; gap: 8px; } .nonimagecenter > .flexttb, .nonimagecenter > .flexltr { - padding: 16px; + padding: 16px !important; background: var(--primary-bg); border-radius: 8px; } @@ -1758,6 +1758,12 @@ fieldset input[type="radio"] { .Buttons { flex: 1; } +.settingbuttons.flexltr{ + width: 100%; + .SettingsButton{ + width:auto; + } +} .settingbuttons { flex: none; width: 192px; @@ -1800,7 +1806,8 @@ fieldset input[type="radio"] { } .optionElement, .FormSettings > button { margin: 16px 16px 0 16px; - word-break: break-word; + /* word-break: break-word; */ + overflow: hidden; } .optionElement:has(.optionElement) { margin: 0; @@ -1915,7 +1922,7 @@ fieldset input[type="radio"] { height: calc(100svh - 50px); } .flexspace { - flex-direction: column; + /*flex-direction: column;*/ } .optionElement input[type="text"], .optionElement textarea, diff --git a/translations/en.json b/translations/en.json index e1629e4d..22ce38db 100644 --- a/translations/en.json +++ b/translations/en.json @@ -152,7 +152,7 @@ "nsfw:":"NSFW:", "selectType":"Select channel type", "selectName":"Name of channel", - "selectCatName":"Name of channel", + "selectCatName":"Name of category", "createChannel":"Create channel", "createCatagory":"Create category" }, @@ -219,7 +219,7 @@ "selectnoti":"Select notifications type", "all":"all", "onlyMentions":"only mentions", - "none":"node", + "none":"none", "confirmLeave":"Are you sure you want to leave?", "yesLeave":"Yes, I'm sure", "noLeave":"Nevermind", @@ -231,7 +231,8 @@ "loadingDiscovery":"Loading...", "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}", "emptytitle":"Weird spot", - "emptytext":"You're in a weird spot, this guild has no channels" + "emptytext":"You're in a weird spot, this guild has no channels", + "default":"Default ($1)" }, "role":{ "displaySettings":"Display settings", @@ -345,7 +346,9 @@ "longInvitedBy":"$1 invited you to join $2", "loginOrCreateAccount":"Login or create an account ⇌", "joinUsing":"Join using invite", - "inviteLinkCode":"Invite Link/Code" + "inviteLinkCode":"Invite Link/Code", + "subtext":"to $1 in $2", + "expireAfter":"Expire after:" }, "friends":{ "blocked":"Blocked", From cee4c62a215d216adafe7205d5cf491c788c95d7 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 18:18:18 -0600 Subject: [PATCH 0091/1330] update name --- src/webpage/channel.ts | 6 +++--- src/webpage/guild.ts | 12 ++++++------ src/webpage/localuser.ts | 8 ++++---- src/webpage/login.ts | 4 ++-- src/webpage/markdown.ts | 4 ++-- src/webpage/member.ts | 6 +++--- src/webpage/message.ts | 4 ++-- src/webpage/settings.ts | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index b798ed67..4c4a8150 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -5,7 +5,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; import{ Permissions }from"./permissions.js"; -import{ BDialog, Settings }from"./settings.js"; +import{ Dialog, Settings }from"./settings.js"; import{ Role, RoleList }from"./role.js"; import{ InfiniteScroller }from"./infiniteScroller.js"; import{ SnowFlake }from"./snowflake.js"; @@ -142,7 +142,7 @@ class Channel extends SnowFlake{ }); }; update(); - const inviteOptions=new BDialog("",{noSubmit:true}); + const inviteOptions=new Dialog("",{noSubmit:true}); inviteOptions.options.addTitle(I18n.getTranslation("inviteOptions.title")); inviteOptions.options.addText(I18n.getTranslation("invite.subtext",this.name,this.guild.properties.name)); @@ -925,7 +925,7 @@ class Channel extends SnowFlake{ setnotifcation(){ const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) const options=["all", "onlyMentions", "none","default"].map(e=>I18n.getTranslation("guild."+e,defualt)); - const notiselect=new BDialog(""); + const notiselect=new Dialog(""); const form=notiselect.options.addForm("",(_,sent:any)=>{ notiselect.hide(); console.log(sent); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 48118ef2..5cfad521 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -3,7 +3,7 @@ import{ Localuser }from"./localuser.js"; import{ Contextmenu }from"./contextmenu.js"; import{ Role, RoleList }from"./role.js"; import{ Member }from"./member.js"; -import{ BDialog, Settings }from"./settings.js"; +import{ Dialog, Settings }from"./settings.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson,}from"./jsontypes.js"; @@ -261,7 +261,7 @@ class Guild extends SnowFlake{ setnotifcation(){ const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e)); - const notiselect=new BDialog(""); + const notiselect=new Dialog(""); const form=notiselect.options.addForm("",(_,sent:any)=>{ notiselect.hide(); this.message_notifications = sent.message_notifications; @@ -277,7 +277,7 @@ class Guild extends SnowFlake{ notiselect.show(); } confirmleave(){ - const full = new BDialog(""); + const full = new Dialog(""); full.options.addTitle(I18n.getTranslation("guild.confirmLeave")) const options=full.options.addOptions("",{ltr:true}); options.addButtonInput("",I18n.getTranslation("guild.yesLeave"),()=>{ @@ -434,7 +434,7 @@ class Guild extends SnowFlake{ confirmDelete(){ let confirmname = ""; - const full = new BDialog(""); + const full = new Dialog(""); full.options.addTitle(I18n.getTranslation("guild.confirmDelete",this.properties.name)); full.options.addTextInput(I18n.getTranslation("guild.serverName"),()=>{}).onchange=(e)=>confirmname=e; @@ -634,7 +634,7 @@ class Guild extends SnowFlake{ createchannels(func = this.createChannel){ const options=["text", "announcement","voice"].map(e=>I18n.getTranslation("channel."+e)); - const channelselect=new BDialog(""); + const channelselect=new Dialog(""); const form=channelselect.options.addForm("",(e:any)=>{ func(e.name,e.type); channelselect.hide(); @@ -646,7 +646,7 @@ class Guild extends SnowFlake{ } createcategory(){ const category = 4; - const channelselect=new BDialog(""); + const channelselect=new Dialog(""); const options=channelselect.options; const form=options.addForm("",(e:any)=>{ this.createChannel(e.name, category); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 79bb0093..43501123 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -6,7 +6,7 @@ import{ User }from"./user.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{ BDialog, Form, FormError, Options, Settings }from"./settings.js"; +import{ Dialog, Form, FormError, Options, Settings }from"./settings.js"; import{ getTextNodeAtPosition, MarkDown }from"./markdown.js"; import { Bot } from "./bot.js"; import { Role } from "./role.js"; @@ -892,7 +892,7 @@ class Localuser{ } createGuild(){ - const full=new BDialog(""); + const full=new Dialog(""); const buttons=full.options.addButtons("",{top:true}); const viacode=buttons.add(I18n.getTranslation("invite.joinUsing")); { @@ -944,7 +944,7 @@ class Localuser{ const content = document.createElement("div"); content.classList.add("flexttb","guildy"); content.textContent = I18n.getTranslation("guild.loadingDiscovery"); - const full = new BDialog(""); + const full = new Dialog(""); full.options.addHTMLArea(content); full.show(); @@ -2096,7 +2096,7 @@ class Localuser{ headers: this.headers, }); const json = await res.json(); - const dialog = new BDialog(""); + const dialog = new Dialog(""); dialog.options.addTitle(I18n.getTranslation("instanceStats.name",this.instancePing.name)); dialog.options.addText(I18n.getTranslation("instanceStats.users",json.counts.user)); dialog.options.addText(I18n.getTranslation("instanceStats.servers",json.counts.guild)); diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 3e578a27..a85a36af 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,5 +1,5 @@ import { I18n } from "./i18n.js"; -import { BDialog, FormError } from "./settings.js"; +import { Dialog, FormError } from "./settings.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); @@ -517,7 +517,7 @@ async function login(username: string, password: string, captcha: string){ }else{ console.log(response); if(response.ticket){ - const better=new BDialog(""); + const better=new Dialog(""); const form=better.options.addForm("",(res:any)=>{ if(res.message){ throw new FormError(ti,res.message); diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 6bddf492..5072e5c1 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -4,7 +4,7 @@ import{ Guild }from"./guild.js"; import { I18n } from "./i18n.js"; import{ Localuser }from"./localuser.js"; import{ Member }from"./member.js"; -import { BDialog } from "./settings.js"; +import { Dialog } from "./settings.js"; class MarkDown{ txt: string[]; @@ -781,7 +781,7 @@ txt[j + 1] === undefined) if(this.trustedDomains.has(Url.host)){ open(); }else{ - const full=new BDialog(""); + const full=new Dialog(""); full.options.addTitle(I18n.getTranslation("leaving")); full.options.addText(I18n.getTranslation("goingToURL",Url.host)); const options=full.options.addOptions("",{ltr:true}); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 47ccbaae..e9240b78 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -4,7 +4,7 @@ import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, presencejson }from"./jsontypes.js"; import { I18n } from "./i18n.js"; -import { BDialog } from "./settings.js"; +import { Dialog } from "./settings.js"; class Member extends SnowFlake{ static already = {}; @@ -229,7 +229,7 @@ class Member extends SnowFlake{ return this.nick || this.user.username; } kick(){ - const menu = new BDialog(""); + const menu = new Dialog(""); const form=menu.options.addForm("",((e:any)=>{ this.kickAPI(e.reason); menu.hide(); @@ -247,7 +247,7 @@ class Member extends SnowFlake{ }); } ban(){ - const menu = new BDialog(""); + const menu = new Dialog(""); const form=menu.options.addForm("",((e:any)=>{ this.banAPI(e.reason); menu.hide(); diff --git a/src/webpage/message.ts b/src/webpage/message.ts index d8ba76f3..843a0cbd 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -13,7 +13,7 @@ import{ Emoji }from"./emoji.js"; import{ mobile }from"./login.js"; import { I18n } from "./i18n.js"; import { Hover } from "./hover.js"; -import { BDialog } from "./settings.js"; +import { Dialog } from "./settings.js"; class Message extends SnowFlake{ static contextmenu = new Contextmenu("message menu"); @@ -691,7 +691,7 @@ class Message extends SnowFlake{ } } confirmDelete(){ - const diaolog=new BDialog(""); + const diaolog=new Dialog(""); diaolog.options.addTitle(I18n.getTranslation("deleteConfirm")); const options=diaolog.options.addOptions("",{ltr:true}); options.addButtonInput("",I18n.getTranslation("yes"),()=>{ diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index e99d6656..28fd1f06 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -589,7 +589,7 @@ class Float{ return this.options.generateHTML(); } } -class BDialog{ +class Dialog{ float:Float; get options(){ return this.float.options; @@ -620,7 +620,7 @@ class BDialog{ background.remove(); } } -export{BDialog}; +export{Dialog}; class Options implements OptionsElement{ name: string; haschanged = false; From 4b361ad56c78a527bfdd524b9319dedd1508648d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 18:24:54 -0600 Subject: [PATCH 0092/1330] slight CSS fixes --- src/webpage/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index cc156621..5b7e2bee 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1760,6 +1760,8 @@ fieldset input[type="radio"] { } .settingbuttons.flexltr{ width: 100%; + border-right: 0; + border-radius: .04in; .SettingsButton{ width:auto; } From 72a918b706f84e669e0eca27bd26e9d4b73e99cc Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 20:28:20 -0600 Subject: [PATCH 0093/1330] css fix --- src/webpage/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 5b7e2bee..d88855e3 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1810,6 +1810,7 @@ fieldset input[type="radio"] { margin: 16px 16px 0 16px; /* word-break: break-word; */ overflow: hidden; + flex-shrink: 0; } .optionElement:has(.optionElement) { margin: 0; @@ -1924,7 +1925,7 @@ fieldset input[type="radio"] { height: calc(100svh - 50px); } .flexspace { - /*flex-direction: column;*/ + /* flex-direction: column; */ } .optionElement input[type="text"], .optionElement textarea, From ba4605476a5482d051be6e0c499728075051b8d7 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 21:06:43 -0600 Subject: [PATCH 0094/1330] css fixes --- src/webpage/settings.ts | 3 +++ src/webpage/style.css | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 28fd1f06..86d65985 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -851,6 +851,9 @@ class Options implements OptionsElement{ generateHTML(): HTMLElement{ const div = document.createElement("div"); div.classList.add("flexttb","titlediv"); + if(this.owner instanceof Options){ + div.classList.add("optionElement"); + } const title = document.createElement("h2"); title.textContent = this.name; div.append(title); diff --git a/src/webpage/style.css b/src/webpage/style.css index d88855e3..3292dc23 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1812,8 +1812,11 @@ fieldset input[type="radio"] { overflow: hidden; flex-shrink: 0; } + .flexltr > .optionElement{ + margin: 16px 6px 0 0px; + } .optionElement:has(.optionElement) { - margin: 0; + /* margin: 0; */ } .optionElement:has(.Buttons) { height: 100%; From 317a329acb31f414b09fc636f20e2f1c4e37f9e1 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 21:07:46 -0600 Subject: [PATCH 0095/1330] undo "fix" --- src/webpage/style.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 3292dc23..a439a926 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1801,6 +1801,7 @@ fieldset input[type="radio"] { .flexspace { overflow-y: auto; padding-bottom: 32px; + flex-shrink: 0; } .flexspace:has(.flexspace) { height: 100%; @@ -1812,11 +1813,14 @@ fieldset input[type="radio"] { overflow: hidden; flex-shrink: 0; } - .flexltr > .optionElement{ +.flexltr:has(.optionElement){ + /* margin: 16px 16px 0 16px !important; */ +} +.flexltr > .optionElement{ margin: 16px 6px 0 0px; - } +} .optionElement:has(.optionElement) { - /* margin: 0; */ + margin: 0; } .optionElement:has(.Buttons) { height: 100%; From f6e1cba74671e82d614606d8a8eea842c54dfa87 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 21:12:26 -0600 Subject: [PATCH 0096/1330] fix css --- src/webpage/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index a439a926..08ac9b6c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1801,7 +1801,7 @@ fieldset input[type="radio"] { .flexspace { overflow-y: auto; padding-bottom: 32px; - flex-shrink: 0; + /* flex-shrink: 0; */ } .flexspace:has(.flexspace) { height: 100%; From 58b8a3f6928628d91ca40a8a791138f42e524a8b Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 27 Nov 2024 21:21:07 -0600 Subject: [PATCH 0097/1330] mentions on new lines --- src/webpage/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpage/style.css b/src/webpage/style.css index 08ac9b6c..1632d359 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1204,6 +1204,9 @@ span .quote:last-of-type .quoteline { border-radius: 4px; cursor: pointer; } +.mentionMD::after{ + content:"​"; +} .mentionMD:hover { background: var(--mention); } From d9ee4d79bc2a25c28a982c809446263f8e19c358 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Wed, 27 Nov 2024 22:42:42 -0500 Subject: [PATCH 0098/1330] edits to the dialogs, added back break-word --- src/webpage/style.css | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index 08ac9b6c..4146f580 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -64,6 +64,9 @@ body { p, h1, h2, h3, pre, form { margin: 0; } +h2:empty { + display: none; +} .title { font-size: 1.5rem; font-weight: bold; @@ -1619,10 +1622,14 @@ img.bigembedimg { gap: 8px; } .nonimagecenter > .flexttb, .nonimagecenter > .flexltr { - padding: 16px !important; + padding: 0 0 16px 0 !important; background: var(--primary-bg); border-radius: 8px; } +.nonimagecenter .settingstitle { + padding-bottom: 0; + border-bottom: none; +} .nonimagecenter button { height: 100%; } @@ -1800,8 +1807,6 @@ fieldset input[type="radio"] { } .flexspace { overflow-y: auto; - padding-bottom: 32px; - /* flex-shrink: 0; */ } .flexspace:has(.flexspace) { height: 100%; @@ -1809,15 +1814,17 @@ fieldset input[type="radio"] { } .optionElement, .FormSettings > button { margin: 16px 16px 0 16px; - /* word-break: break-word; */ + word-break: break-word; overflow: hidden; flex-shrink: 0; } -.flexltr:has(.optionElement){ - /* margin: 16px 16px 0 16px !important; */ +.flexltr:has(>.optionElement) { + margin: 16px 16px 0 16px; + flex-wrap: wrap; + gap: 6px; } -.flexltr > .optionElement{ - margin: 16px 6px 0 0px; +.flexltr > .optionElement { + margin: 0; } .optionElement:has(.optionElement) { margin: 0; From c2ccc22adeb60d2ff9b0eb6af3f10d1017651b95 Mon Sep 17 00:00:00 2001 From: ygg2 Date: Wed, 27 Nov 2024 23:24:14 -0500 Subject: [PATCH 0099/1330] added back bottom space on flexspace --- src/webpage/style.css | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/webpage/style.css b/src/webpage/style.css index ee187e02..021d98ad 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1633,6 +1633,9 @@ img.bigembedimg { padding-bottom: 0; border-bottom: none; } +.nonimagecenter .flexspace, .nonimagecenter .FormSettings { + padding: 0; +} .nonimagecenter button { height: 100%; } @@ -1809,12 +1812,19 @@ fieldset input[type="radio"] { height: 100%; } .flexspace { + padding-bottom: 32px; overflow-y: auto; } .flexspace:has(.flexspace) { height: 100%; padding-bottom: 0; } +.FormSettings .flexspace { + padding-bottom: 0; +} +.FormSettings { + padding-bottom: 32px; +} .optionElement, .FormSettings > button { margin: 16px 16px 0 16px; word-break: break-word; @@ -1941,8 +1951,8 @@ fieldset input[type="radio"] { right: 0; height: calc(100svh - 50px); } - .flexspace { - /* flex-direction: column; */ + .flexspace:has(.hypoprofile) { + flex-direction: column; } .optionElement input[type="text"], .optionElement textarea, @@ -2071,6 +2081,9 @@ fieldset input[type="radio"] { height: 100px; width: 100%; } + .nonimagecenter .settingbuttons { + height: auto; + } .fixedsearch { top: 50% !important; left: 50% !important; From a65a1ec03c22f4a7781aa09de9d0e45f61a6d004 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 28 Nov 2024 13:20:09 +0100 Subject: [PATCH 0100/1330] Localisation updates from https://translatewiki.net. --- translations/ko.json | 17 ++++++++++++++++- translations/ru.json | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/translations/ko.json b/translations/ko.json index 69f889a9..45116978 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -181,7 +181,8 @@ "message": { "reactionAdd": "반응 추가", "delete": "메시지 삭제", - "edit": "메시지 편집" + "edit": "메시지 편집", + "edited": "(편집됨)" }, "instanceStats": { "name": "인스턴스 통계: $1", @@ -209,6 +210,19 @@ "longInvitedBy": "$1님이 당신을 $2에 초대했습니다", "inviteLinkCode": "초대 링크/코드" }, + "friends": { + "blocked": "차단됨", + "blockedusers": "차단된 사용자:", + "addfriend": "친구 추가", + "notfound": "사용자를 찾을 수 없습니다", + "pending": "보류 중", + "all": "모두", + "all:": "모든 친구:", + "online": "온라인", + "online:": "온라인 친구:", + "friendlist": "친구 목록", + "friends": "친구" + }, "DMs": { "copyId": "DM ID 복사", "markRead": "읽은 것으로 표시", @@ -232,5 +246,6 @@ "member": { "reason:": "이유:" }, + "uploadFilesText": "여기에 파일을 올리세요!", "retrying": "다시 시도하는 중..." } diff --git a/translations/ru.json b/translations/ru.json index 21a2abdb..28f989d7 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -4,6 +4,8 @@ "locale": "ru", "comment": "Русский перевод Jank Client", "authors": [ + "Kareyac", + "Okras", "StealthTheAB", "StealthTheAngryBird" ] @@ -152,7 +154,7 @@ "nsfw:": "Возрастное ограничение:", "selectType": "Выберите тип канала", "selectName": "Название канала", - "selectCatName": "Название канала", + "selectCatName": "Название категории", "createChannel": "Создать канал", "createCatagory": "Создать категорию" }, @@ -229,7 +231,8 @@ "noDelete": "Не сейчас", "create": "Создать гильдию", "loadingDiscovery": "Загрузка...", - "disoveryTitle": "Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}" + "disoveryTitle": "Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}", + "default": "По умолчанию ($1)" }, "role": { "displaySettings": "Настройки отображения", @@ -311,7 +314,8 @@ "message": { "reactionAdd": "Добавить реакцию", "delete": "Удалить сообщение", - "edit": "Редактировать сообщение" + "edit": "Редактировать сообщение", + "edited": "(отредактировано)" }, "instanceStats": { "name": "Статистика инстанции: $1", @@ -344,6 +348,14 @@ "joinUsing": "Присоединиться с помощью приглашения", "inviteLinkCode": "Ссылка-приглашение/Код" }, + "friends": { + "addfriend": "Добавить в друзья", + "addfriendpromt": "Добавить друзей по имени пользователя:", + "notfound": "Пользователь не найден", + "all:": "Все друзья:", + "friendlist": "Список друзей", + "friends": "Друзья" + }, "replyingTo": "В ответ $1", "DMs": { "copyId": "Копировать ID ЛС", @@ -374,6 +386,7 @@ "reason:": "Причина:", "ban": "Забанить $1 из $2" }, + "uploadFilesText": "Загрузите свои файлы сюда!", "errorReconnect": "Не удалось подключиться к серверу, повтор попытки через $1 секунд...", "retrying": "Повтор...", "unableToConnect": "Не удалось подключиться к серверу Spacebar. Пожалуйста, попробуйте выйти и ещё раз войти." From 7e82d783c79fee544aa280678c7deebc0da6d0cb Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:02:39 -0600 Subject: [PATCH 0101/1330] fix infiniate scroller stuff --- src/webpage/infiniteScroller.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 55202d8b..19ad2b0c 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -21,6 +21,12 @@ offset: number changePromise: Promise | undefined; scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; + resetVars(){ + this.scrollTop=0; + this.scrollBottom=0; + this.averageheight=60; + this.watchtime=false; + } constructor( getIDFromOffset: InfiniteScroller["getIDFromOffset"], getHTMLFromID: InfiniteScroller["getHTMLFromID"], @@ -268,9 +274,9 @@ offset: number return await this.changePromise; } - async focus(id: string, flash = true): Promise{ let element: HTMLElement | undefined; + this.resetVars(); for(const thing of this.HTMLElements){ if(thing[1] === id){ element = thing[0]; @@ -313,6 +319,7 @@ offset: number this.div.remove(); this.div = null; } + this.resetVars(); try{ for(const thing of this.HTMLElements){ await this.destroyFromID(thing[1]); From f682267a1ba459b46bd8493e26b0b02e10ab311a Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:09:41 -0600 Subject: [PATCH 0102/1330] more fixes --- src/webpage/infiniteScroller.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 19ad2b0c..49801a9a 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -26,6 +26,13 @@ offset: number this.scrollBottom=0; this.averageheight=60; this.watchtime=false; + this.needsupdate=true; + this.beenloaded=false; + this.changePromise=undefined; + if(this.timeout){ + clearTimeout(this.timeout); + this.timeout=null; + } } constructor( getIDFromOffset: InfiniteScroller["getIDFromOffset"], @@ -43,7 +50,7 @@ offset: number if(this.div){ throw new Error("Div already exists, exiting."); } - + this.resetVars(); const scroll = document.createElement("div"); scroll.classList.add("scroller"); this.div = scroll; From 8995d0f12677290b30319c805816a434b97e7295 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:13:34 -0600 Subject: [PATCH 0103/1330] even more fixes --- src/webpage/infiniteScroller.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 49801a9a..d63ee586 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -33,6 +33,11 @@ offset: number clearTimeout(this.timeout); this.timeout=null; } + for(const thing of this.HTMLElements){ + this.destroyFromID(thing[1]); + } + this.HTMLElements=[]; + this.div=null; } constructor( getIDFromOffset: InfiniteScroller["getIDFromOffset"], @@ -283,7 +288,6 @@ offset: number } async focus(id: string, flash = true): Promise{ let element: HTMLElement | undefined; - this.resetVars(); for(const thing of this.HTMLElements){ if(thing[1] === id){ element = thing[0]; From d245b645f731d8b73493ba8476ae2f240eacf313 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:16:40 -0600 Subject: [PATCH 0104/1330] remove some logging --- src/webpage/channel.ts | 2 -- src/webpage/file.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 4c4a8150..1ac1167e 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -761,7 +761,6 @@ class Channel extends SnowFlake{ } } async getmessage(id: string): Promise{ - console.log("getting:"+id); const message = this.messages.get(id); if(message){ return message; @@ -771,7 +770,6 @@ class Channel extends SnowFlake{ { headers: this.headers } ); const json = await gety.json(); - console.log(json); return new Message(json[0], this); } } diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 25a6b34d..a16dcb57 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -49,8 +49,6 @@ class File{ div.style.width = this.width + "px"; div.style.height = this.height + "px"; } - console.log(img); - console.log(this.width, this.height); return div; }else if(this.content_type.startsWith("video/")){ const video = document.createElement("video"); From 0b346a69c8867720dec496b82a0cf4a1cdc09fff Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:22:27 -0600 Subject: [PATCH 0105/1330] more fixes --- src/webpage/infiniteScroller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index d63ee586..43a61ca8 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -311,6 +311,7 @@ offset: number element.scrollIntoView(); } }else{ + this.resetVars(); for(const thing of this.HTMLElements){ await this.destroyFromID(thing[1]); } From e94b5cb2d5eaf251fee6e3279f96f24d9a1b2e01 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 13:51:38 -0600 Subject: [PATCH 0106/1330] add comment --- src/webpage/infiniteScroller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 43a61ca8..34cbd833 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -312,6 +312,7 @@ offset: number } }else{ this.resetVars(); + //TODO may be a redundent loop, not 100% sure :P for(const thing of this.HTMLElements){ await this.destroyFromID(thing[1]); } From 80cf770c8f893c7b6a2eec673ca9698c39136077 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 14:47:54 -0600 Subject: [PATCH 0107/1330] typo fix --- src/webpage/infiniteScroller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 34cbd833..5332bb42 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -312,7 +312,7 @@ offset: number } }else{ this.resetVars(); - //TODO may be a redundent loop, not 100% sure :P + //TODO may be a redundant loop, not 100% sure :P for(const thing of this.HTMLElements){ await this.destroyFromID(thing[1]); } From c66ee79241254ebf725ce8d9fbc404ade764e81d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 17:18:48 -0600 Subject: [PATCH 0108/1330] settings update :3 --- src/webpage/channel.ts | 2 + src/webpage/guild.ts | 135 ++++++++++++++++++++++++++++++++++++++-- src/webpage/settings.ts | 24 ++++++- src/webpage/style.css | 2 + translations/en.json | 14 ++++- 5 files changed, 169 insertions(+), 8 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 1ac1167e..9493308c 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -71,6 +71,8 @@ class Channel extends SnowFlake{ this.contextmenu.addbutton(()=>I18n.getTranslation("channel.settings"), function(this: Channel){ this.generateSettings(); + },null,function(){ + return this.hasPermission("MANAGE_CHANNELS"); }); this.contextmenu.addbutton( diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 5cfad521..d50274ea 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -3,7 +3,7 @@ import{ Localuser }from"./localuser.js"; import{ Contextmenu }from"./contextmenu.js"; import{ Role, RoleList }from"./role.js"; import{ Member }from"./member.js"; -import{ Dialog, Settings }from"./settings.js"; +import{ Dialog, Options, Settings }from"./settings.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson,}from"./jsontypes.js"; @@ -67,13 +67,21 @@ class Guild extends SnowFlake{ Guild.contextmenu.addbutton( ()=>I18n.getTranslation("guild.makeInvite"), - function(this: Guild){}, + function(this: Guild){ + const d=new Dialog(""); + this.makeInviteMenu(d.options); + d.show(); + }, null, _=>true, - _=>false + function(){ + return this.member.hasPermission("CREATE_INSTANT_INVITE"); + } ); Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.settings"), function(this: Guild){ this.generateSettings(); + },null,function(){ + return this.member.hasPermission("MANAGE_GUILD"); }); /* -----things left for later----- guild.contextmenu.addbutton("Leave Guild",function(){ @@ -88,6 +96,10 @@ class Guild extends SnowFlake{ } generateSettings(){ const settings = new Settings(I18n.getTranslation("guild.settingsFor",this.properties.name)); + const textChannels=this.channels.filter(e=>{ + //TODO there are almost certainly more types. is Voice valid? + return new Set([0,5]).has(e.type); + }); { const overview = settings.addButton(I18n.getTranslation("guild.overview")); const form = overview.addForm("", _=>{}, { @@ -97,17 +109,60 @@ class Guild extends SnowFlake{ method: "PATCH", }); form.addTextInput(I18n.getTranslation("guild.name:"), "name", { initText: this.properties.name }); - form.addMDInput("Description:", "description", { + form.addMDInput(I18n.getTranslation("guild.description:"), "description", { initText: this.properties.description, }); + form.addFileInput(I18n.getTranslation("guild.banner:"), "banner", { clear: true }); form.addFileInput(I18n.getTranslation("guild.icon:"), "icon", { clear: true }); + + form.addHR(); + + const sysmap=[null,...textChannels.map(e=>e.id)]; + form.addSelect(I18n.getTranslation("guild.systemSelect:"), "system_channel_id", + ["No system messages",...textChannels.map(e=>e.name)],{defaultIndex:sysmap.indexOf(this.properties.system_channel_id)} + ,sysmap); + + form.addCheckboxInput(I18n.getTranslation("guild.sendrandomwelcome?"),"s1",{ + initState:!(this.properties.system_channel_flags&1) + }); + form.addCheckboxInput(I18n.getTranslation("guild.stickWelcomeReact?"),"s4",{ + initState:!(this.properties.system_channel_flags&8) + }); + form.addCheckboxInput(I18n.getTranslation("guild.boostMessage?"),"s2",{ + initState:!(this.properties.system_channel_flags&2) + }); + form.addCheckboxInput(I18n.getTranslation("guild.helpTips?"),"s3",{ + initState:!(this.properties.system_channel_flags&4) + }); + form.addPreprocessor((e:any)=>{ + let bits=0; + bits+=(1-e.s1)*1; + delete e.s1; + bits+=(1-e.s2)*2; + delete e.s2; + bits+=(1-e.s3)*4; + delete e.s3; + bits+= (1-e.s4)*8; + delete e.s4; + e.system_channel_flags=bits; + }) + + form.addHR(); + form.addSelect(I18n.getTranslation("guild.defaultNoti"),"default_message_notifications", + [I18n.getTranslation("guild.onlyMentions"),I18n.getTranslation("guild.all")], + { + defaultIndex:[1,0].indexOf(this.properties.default_message_notifications), + radio:true + },[1,0]); + form.addHR(); let region = this.properties.region; if(!region){ region = ""; } form.addTextInput(I18n.getTranslation("guild.region:"), "region", { initText: region }); } + this.makeInviteMenu(settings.addButton(I18n.getTranslation("invite.inviteMaker")),textChannels); const s1 = settings.addButton(I18n.getTranslation("guild.roles")); const permlist: [Role, Permissions][] = []; for(const thing of this.roles){ @@ -118,6 +173,78 @@ class Guild extends SnowFlake{ ); settings.show(); } + makeInviteMenu(options:Options,valid:void|(Channel[])){ + if(!valid){ + valid=this.channels.filter(e=>{ + //TODO there are almost certainly more types. is Voice valid? + return new Set([0,5]).has(e.type); + }); + } + let channel=valid[0]; + const div = document.createElement("div"); + div.classList.add("invitediv"); + const text = document.createElement("span"); + text.classList.add("ellipsis"); + div.append(text); + let uses = 0; + let expires = 1800; + const copycontainer = document.createElement("div"); + copycontainer.classList.add("copycontainer"); + const copy = document.createElement("span"); + copy.classList.add("copybutton", "svgicon", "svg-copy"); + copycontainer.append(copy); + copycontainer.onclick = _=>{ + if(text.textContent){ + navigator.clipboard.writeText(text.textContent); + } + }; + div.append(copycontainer); + const update = ()=>{ + fetch(`${this.info.api}/channels/${channel.id}/invites`, { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + flags: 0, + target_type: null, + target_user_id: null, + max_age: expires + "", + max_uses: uses, + temporary: uses !== 0 + }), + }) + .then(_=>_.json()) + .then(json=>{ + const params = new URLSearchParams(""); + params.set("instance", this.info.wellknown); + const encoded = params.toString(); + text.textContent = `${location.origin}/invite/${json.code}?${encoded}`; + }); + }; + + options.addTitle(I18n.getTranslation("inviteOptions.title")); + const text2=options.addText(""); + options.addSelect(I18n.getTranslation("invite.channel:"),()=>{},valid.map(e=>e.name)) + .watchForChange((e)=>{ + channel=valid[e]; + text2.setText(I18n.getTranslation("invite.subtext",channel.name,this.properties.name)); + }) + + + options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, + ["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) + ).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];}; + + const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) + timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) + options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) + .onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];}; + + options.addButtonInput("",I18n.getTranslation("invite.createInvite"),()=>{ + update(); + }) + + options.addHTMLArea(div); + } roleUpdate:(role:Role,added:-1|0|1)=>unknown=()=>{}; sortRoles(){ this.roles.sort((a,b)=>(b.position-a.position)); diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 86d65985..9d306887 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -804,6 +804,12 @@ class Options implements OptionsElement{ this.generate(text); return text; } + addHR(){ + const rule = new HorrizonalRule(); + this.options.push(rule); + this.generate(rule); + return rule; + } addTitle(str: string){ const text = new SettingsTitle(str); this.options.push(text); @@ -1054,13 +1060,13 @@ class Form implements OptionsElement{ (this.button.deref() as HTMLElement).hidden=false; } } - selectMap=new WeakMap(); + selectMap=new WeakMap(); addSelect( label: string, formName: string, selections: string[], { defaultIndex = 0, required = false,radio=false}={}, - correct:(string|number)[]=selections + correct:(string|number|null)[]=selections ){ const select = this.options.addSelect(label, _=>{}, selections, { defaultIndex,radio @@ -1160,6 +1166,9 @@ class Form implements OptionsElement{ addText(str: string){ return this.options.addText(str); } + addHR(){ + return this.options.addHR(); + } addTitle(str: string){ this.options.addTitle(str); } @@ -1373,6 +1382,17 @@ class Form implements OptionsElement{ element.textContent = message; } } +class HorrizonalRule implements OptionsElement{ + constructor(){} + generateHTML(): HTMLElement { + return document.createElement("hr"); + } + watchForChange (_: (arg1: undefined) => void){ + throw new Error("don't do this") + }; + submit= () => {}; + value=undefined; +} class Settings extends Buttons{ static readonly Buttons = Buttons; static readonly Options = Options; diff --git a/src/webpage/style.css b/src/webpage/style.css index 021d98ad..6e94355b 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -1690,11 +1690,13 @@ fieldset input[type="radio"] { overflow: hidden; display: flex; align-items: center; + position: relative; } .copycontainer { flex: none; background: var(--button-bg); cursor: pointer; + margin-left: auto; } .copycontainer:hover { background: var(--button-hover); diff --git a/translations/en.json b/translations/en.json index 22ce38db..3bf4cde3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -232,7 +232,14 @@ "disoveryTitle":"Guild discovery ($1) {{PLURAL:$1|entry|entries}}", "emptytitle":"Weird spot", "emptytext":"You're in a weird spot, this guild has no channels", - "default":"Default ($1)" + "default":"Default ($1)", + "description:":"Description:", + "systemSelect:":"Systems messages channel:", + "sendrandomwelcome?":"Send a random message when someone joins this guild", + "stickWelcomeReact?":"Prompt members of your guild to react with a sticker when someone joins!", + "boostMessage?":"Send a message when someone boosts your guild!", + "helpTips?":"Send helpful tips for your guild!", + "defaultNoti":"Set the default notification settings of your guild!" }, "role":{ "displaySettings":"Display settings", @@ -348,7 +355,10 @@ "joinUsing":"Join using invite", "inviteLinkCode":"Invite Link/Code", "subtext":"to $1 in $2", - "expireAfter":"Expire after:" + "expireAfter":"Expire after:", + "channel:":"Channel:", + "inviteMaker":"Invite Maker", + "createInvite":"Create invite" }, "friends":{ "blocked":"Blocked", From d406b0e6725de15588e87ad1cd297a56833c6cb1 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 18:25:30 -0600 Subject: [PATCH 0109/1330] handle two more events --- src/webpage/jsontypes.ts | 13 +++++++++++++ src/webpage/localuser.ts | 15 +++++++++++++++ src/webpage/member.ts | 4 ++++ 3 files changed, 32 insertions(+) diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 7a3d7a83..767e91b1 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -528,6 +528,19 @@ roleCreate | { t: "PRESENCE_UPDATE", d: presencejson, s:number +}|{ + op:0, + t:"GUILD_MEMBER_ADD", + d:memberjson, + s:number +}|{ + op:0, + t:"GUILD_MEMBER_REMOVE", + d:{ + guild_id:string, + user:userjson + }, + s:number }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 43501123..e61e01fd 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -562,6 +562,21 @@ class Localuser{ } break; } + case "GUILD_MEMBER_ADD":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + Member.new(temp.d,guild); + break; + } + case "GUILD_MEMBER_REMOVE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + const user=new User(temp.d.user,this); + const member=user.members.get(guild); + if(!(member instanceof Member)) break; + member.remove(); + break; + } default :{ //@ts-ignore console.warn("Unhandled case "+temp.t,temp); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index e9240b78..7fed07c0 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -55,6 +55,10 @@ class Member extends SnowFlake{ return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); }); } + remove(){ + this.user.members.delete(this.guild); + this.guild.members.delete(this); + } update(memberjson: memberjson){ this.roles=[]; for(const key of Object.keys(memberjson)){ From 4509605cc70d28272469eed455cd527932aff2a4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 28 Nov 2024 21:26:06 -0600 Subject: [PATCH 0110/1330] spacing changes --- src/webpage/index.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/webpage/index.html b/src/webpage/index.html index 13f62766..1cf492ec 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -1,6 +1,5 @@ - @@ -16,8 +15,6 @@ - -
From 38ca7ea280229ec637f91baa9aec8e2e50213785 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 29 Nov 2024 16:18:59 -0600 Subject: [PATCH 0111/1330] fix minor message combine bug --- src/webpage/message.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 843a0cbd..7d619330 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -368,9 +368,7 @@ class Message extends SnowFlake{ let next: Message | undefined = this; while(next?.author === this.author){ next.generateMessage(); - next = this.channel.messages.get( - this.channel.idToNext.get(next.id) as string - ); + next = this.channel.messages.get(this.channel.idToNext.get(next.id) as string); } if(this.channel.infinite.scollDiv && scroll){ this.channel.infinite.scollDiv.scrollTop = scroll; @@ -460,10 +458,11 @@ class Message extends SnowFlake{ div.appendChild(replyline); } div.appendChild(build); - if({ 0: true, 19: true }[this.type] || this.attachments.length !== 0){ + const messageTypes=new Set([0,19]) + if(messageTypes.has(this.type) || this.attachments.length !== 0){ const pfpRow = document.createElement("div"); let pfpparent, current; - if(premessage != null){ + if(premessage !== null){ pfpparent ??= premessage; // @ts-ignore // TODO: type this @@ -473,7 +472,7 @@ class Message extends SnowFlake{ const newt = new Date(this.timestamp).getTime() / 1000; current = newt - old > 600; } - const combine = premessage?.author != this.author || current || this.message_reference; + const combine = premessage?.author != this.author || current || this.message_reference || !messageTypes.has(premessage.type); if(combine){ const pfp = this.author.buildpfp(); this.author.bind(pfp, this.guild, false); From 0227320876570fb55aa85925dfa27b0e614de215 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 29 Nov 2024 21:40:20 -0600 Subject: [PATCH 0112/1330] change snowflake --- src/webpage/snowflake.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/webpage/snowflake.ts b/src/webpage/snowflake.ts index e6a46035..e32c9add 100644 --- a/src/webpage/snowflake.ts +++ b/src/webpage/snowflake.ts @@ -10,10 +10,7 @@ abstract class SnowFlake{ try{ return Number((BigInt(str) >> 22n) + 1420070400000n); }catch{ - console.error( - `The ID is corrupted, it's ${str} when it should be some number.` - ); - return 0; + throw new Error(`The ID is corrupted, it's ${str} when it should be some number.`); } } } From a402177e89d82ad4472173a7c37e3e2d168308e8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 29 Nov 2024 22:26:39 -0600 Subject: [PATCH 0113/1330] bug fix --- src/webpage/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 7d619330..7a9a7add 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -462,7 +462,7 @@ class Message extends SnowFlake{ if(messageTypes.has(this.type) || this.attachments.length !== 0){ const pfpRow = document.createElement("div"); let pfpparent, current; - if(premessage !== null){ + if(premessage !== undefined){ pfpparent ??= premessage; // @ts-ignore // TODO: type this From 01ebde20f97866dee3a378ff49178f5046fd23e1 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 16:44:53 -0600 Subject: [PATCH 0114/1330] fix member list bug --- src/webpage/localuser.ts | 18 ++++++++++-------- src/webpage/member.ts | 6 +++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index e61e01fd..d27d44b0 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -652,20 +652,22 @@ class Localuser{ async memberListUpdate(list:memberlistupdatejson|void){ const div=document.getElementById("sideDiv") as HTMLDivElement; div.innerHTML=""; - if(!list) return; - const counts=new Map(); const guild=this.lookingguild; if(!guild) return; const channel=this.channelfocus; if(!channel) return; - for(const thing of list.d.ops[0].items){ - if("member" in thing){ - await Member.new(thing.member,guild); - }else{ - counts.set(thing.group.id,thing.group.count); + if(list){ + const counts=new Map(); + for(const thing of list.d.ops[0].items){ + if("member" in thing){ + await Member.new(thing.member,guild); + }else{ + counts.set(thing.group.id,thing.group.count); + } } } + const elms:Map=new Map([]); for(const role of guild.roles){ if(role.hoist){ @@ -678,7 +680,7 @@ class Localuser{ members.forEach((member)=>{ if(!channel.hasPermission("VIEW_CHANNEL",member)){ members.delete(member); - console.log(member) + console.log(member,"can't see") return; } }) diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 7fed07c0..2afb7882 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -162,7 +162,11 @@ class Member extends SnowFlake{ } }); user.members.set(guild, promise); - return await promise; + const member=await promise; + if(member){ + guild.members.add(member); + } + return member; } if(maybe instanceof Promise){ return await maybe; From e06b28742c084259875773dcaa1be3b6ea064344 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 17:10:34 -0600 Subject: [PATCH 0115/1330] conditionally show add friend in context menu --- src/webpage/user.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 621793f9..003fe4a7 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -163,6 +163,8 @@ class User extends SnowFlake{ ); this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){ this.changeRelationship(1); + },null,function(){ + return this.relationshipType===0; }); this.contextmenu.addbutton( ()=>I18n.getTranslation("user.kick"), From d94c20b275e4a3e82e6935d82e166e678d1160fb Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 19:04:00 -0600 Subject: [PATCH 0116/1330] some reorginization --- src/webpage/{ => audio}/audio.ts | 15 - src/webpage/audio/index.html | 22 ++ src/webpage/audio/page.ts | 0 src/webpage/channel.ts | 4 +- src/webpage/contextmenu.ts | 2 +- src/webpage/embed.ts | 2 +- src/webpage/emoji.ts | 44 +-- src/webpage/home.ts | 2 +- src/webpage/index.ts | 3 +- src/webpage/invite.ts | 3 +- src/webpage/localuser.ts | 18 +- src/webpage/login.ts | 451 +------------------------------ src/webpage/message.ts | 2 +- src/webpage/oauth2/auth.ts | 3 +- src/webpage/register.ts | 3 +- src/webpage/utils/binaryUtils.ts | 79 ++++++ src/webpage/utils/utils.ts | 436 ++++++++++++++++++++++++++++++ 17 files changed, 586 insertions(+), 503 deletions(-) rename src/webpage/{ => audio}/audio.ts (90%) create mode 100644 src/webpage/audio/index.html create mode 100644 src/webpage/audio/page.ts create mode 100644 src/webpage/utils/binaryUtils.ts create mode 100644 src/webpage/utils/utils.ts diff --git a/src/webpage/audio.ts b/src/webpage/audio/audio.ts similarity index 90% rename from src/webpage/audio.ts rename to src/webpage/audio/audio.ts index 13b90cef..cdc412fe 100644 --- a/src/webpage/audio.ts +++ b/src/webpage/audio/audio.ts @@ -1,5 +1,3 @@ -import{ getBulkInfo }from"./login.js"; - class AVoice{ audioCtx: AudioContext; info: { wave: string | Function; freq: number }; @@ -72,10 +70,6 @@ class AVoice{ return(_t: number, _freq: number)=>{ return Math.random() * 2 - 1; }; - case"noise": - return(_t: number, _freq: number)=>{ - return 0; - }; } return new Function(); } @@ -183,14 +177,5 @@ class AVoice{ static get sounds(){ return["three", "zip", "square", "beep"]; } - static setNotificationSound(sound: string){ - const userinfos = getBulkInfo(); - userinfos.preferences.notisound = sound; - localStorage.setItem("userinfos", JSON.stringify(userinfos)); - } - static getNotificationSound(){ - const userinfos = getBulkInfo(); - return userinfos.preferences.notisound; - } } export{ AVoice as AVoice }; diff --git a/src/webpage/audio/index.html b/src/webpage/audio/index.html new file mode 100644 index 00000000..429291ea --- /dev/null +++ b/src/webpage/audio/index.html @@ -0,0 +1,22 @@ + + + + + + + Jank Audio + + + + + + + + + + +

Place holder text

+ + + + diff --git a/src/webpage/audio/page.ts b/src/webpage/audio/page.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 9493308c..42717deb 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1,6 +1,6 @@ "use strict"; import{ Message }from"./message.js"; -import{ AVoice }from"./audio.js"; +import{ AVoice }from"./audio/audio.js"; import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; @@ -1403,7 +1403,7 @@ class Channel extends SnowFlake{ ); } notify(message: Message, deep = 0){ - AVoice.noises(AVoice.getNotificationSound()); + AVoice.noises(this.localuser.getNotificationSound()); if(!("Notification" in window)){ }else if(Notification.permission === "granted"){ let noticontent: string | undefined | null = message.content.textContent; diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 8d6eac38..794fd88e 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,4 +1,4 @@ -import{ iOS }from"./login.js"; +import{ iOS }from"./utils/utils.js"; class Contextmenu{ static currentmenu: HTMLElement | ""; name: string; diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index e84105d2..73bad4a2 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -1,7 +1,7 @@ import{ Message }from"./message.js"; import{ MarkDown }from"./markdown.js"; import{ embedjson, invitejson }from"./jsontypes.js"; -import{ getapiurls, getInstances }from"./login.js"; +import{ getapiurls, getInstances }from"./utils/utils.js"; import{ Guild }from"./guild.js"; import { I18n } from "./i18n.js"; import { ImagesDisplay } from "./disimg.js"; diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 03b1d429..b6e04e82 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -2,6 +2,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import { emojijson } from "./jsontypes.js"; import{ Localuser }from"./localuser.js"; +import { BinRead } from "./utils/binaryUtils.js"; //I need to recompile the emoji format for translation class Emoji{ @@ -64,51 +65,24 @@ class Emoji{ } } static decodeEmojiList(buffer: ArrayBuffer){ - const view = new DataView(buffer, 0); - let i = 0; - function read16(){ - const int = view.getUint16(i); - i += 2; - return int; - } - function read8(){ - const int = view.getUint8(i); - i += 1; - return int; - } - function readString8(){ - return readStringNo(read8()); - } - function readString16(){ - return readStringNo(read16()); - } - function readStringNo(length: number){ - const array = new Uint8Array(length); - - for(let i = 0; i < length; i++){ - array[i] = read8(); - } - //console.log(array); - return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer); - } - const build: { name: string; emojis: { name: string; emoji: string }[] }[] = - []; - let cats = read16(); + const reader=new BinRead(buffer) + const build: { name: string; emojis: { name: string; emoji: string }[] }[] = []; + let cats = reader.read16(); for(; cats !== 0; cats--){ - const name = readString16(); + const name = reader.readString16(); const emojis: { name: string; skin_tone_support: boolean; emoji: string; }[] = []; - let emojinumber = read16(); + let emojinumber = reader.read16(); for(; emojinumber !== 0; emojinumber--){ //console.log(emojis); - const name = readString8(); - const len = read8(); + const name = reader.readString8(); + const len = reader.read8(); const skin_tone_support = len > 127; - const emoji = readStringNo(len - Number(skin_tone_support) * 128); + const emoji = reader.readStringNo(len - Number(skin_tone_support) * 128); emojis.push({ name, skin_tone_support, diff --git a/src/webpage/home.ts b/src/webpage/home.ts index a04772b4..20cac876 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -1,5 +1,5 @@ import { I18n } from "./i18n.js"; -import{ mobile }from"./login.js"; +import{ mobile }from"./utils/utils.js"; console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 6499fcbc..b7fa3bb1 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -1,6 +1,7 @@ import{ Localuser }from"./localuser.js"; import{ Contextmenu }from"./contextmenu.js"; -import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js"; +import{ mobile }from"./utils/utils.js"; +import { getBulkUsers, setTheme, Specialuser } from "./utils/utils.js"; import{ MarkDown }from"./markdown.js"; import{ Message }from"./message.js"; import{File}from"./file.js"; diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index 15ebecaa..fcc053b4 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -1,5 +1,6 @@ import { I18n } from "./i18n.js"; -import{ getBulkUsers, Specialuser, getapiurls }from"./login.js"; +import{ getapiurls }from"./utils/utils.js"; +import { getBulkUsers, Specialuser } from "./utils/utils.js"; (async ()=>{ const users = getBulkUsers(); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index d27d44b0..b7b4087c 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1,9 +1,10 @@ import{ Guild }from"./guild.js"; import{ Channel }from"./channel.js"; import{ Direct }from"./direct.js"; -import{ AVoice }from"./audio.js"; +import{ AVoice }from"./audio/audio.js"; import{ User }from"./user.js"; -import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js"; +import{ getapiurls, SW }from"./utils/utils.js"; +import { getBulkInfo, setTheme, Specialuser } from "./utils/utils.js"; import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; import{ Member }from"./member.js"; import{ Dialog, Form, FormError, Options, Settings }from"./settings.js"; @@ -1237,10 +1238,10 @@ class Localuser{ .addSelect( I18n.getTranslation("localuser.notisound"), _=>{ - AVoice.setNotificationSound(sounds[_]); + this.setNotificationSound(sounds[_]); }, sounds, - { defaultIndex: sounds.indexOf(AVoice.getNotificationSound()) } + { defaultIndex: sounds.indexOf(this.getNotificationSound()) } ) .watchForChange(_=>{ AVoice.noises(sounds[_]); @@ -2121,5 +2122,14 @@ class Localuser{ dialog.options.addText(I18n.getTranslation("instanceStats.members",json.counts.members)); dialog.show(); } + setNotificationSound(sound: string){ + const userinfos = getBulkInfo(); + userinfos.preferences.notisound = sound; + localStorage.setItem("userinfos", JSON.stringify(userinfos)); + } + getNotificationSound(){ + const userinfos = getBulkInfo(); + return userinfos.preferences.notisound; + } } export{ Localuser }; diff --git a/src/webpage/login.ts b/src/webpage/login.ts index a85a36af..099b67eb 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,95 +1,12 @@ +import { getBulkInfo, Specialuser } from "./utils/utils.js"; import { I18n } from "./i18n.js"; import { Dialog, FormError } from "./settings.js"; +import { checkInstance } from "./utils/utils.js"; -const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); -const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); -let instances: -| { -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; -}; -}[] -| null; -const datalist = document.getElementById("instances"); -console.warn(datalist); -const instancefetch=fetch("/instances.json") - .then(res=>res.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; - } - }[] - )=>{ - instances = json; - if(datalist){ - console.warn(json); - if(instancein && instancein.value === ""){ - instancein.value = json[0].name; - } - for(const instance of json){ - if(instance.display === false){ - continue; - } - const option = document.createElement("option"); - option.disabled = !instance.online; - option.value = instance.name; - if(instance.url){ - stringURLMap.set(option.value, instance.url); - if(instance.urls){ - stringURLsMap.set(instance.url, instance.urls); - } - }else if(instance.urls){ - stringURLsMap.set(option.value, instance.urls); - }else{ - option.disabled = true; - } - if(instance.description){ - option.label = instance.description; - }else{ - option.label = instance.name; - } - datalist.append(option); - } - checkInstance(""); - } - } - ); -setTheme(); -await I18n.done -function setTheme(){ - let name = localStorage.getItem("theme"); - if(!name){ - localStorage.setItem("theme", "Dark"); - name = "Dark"; - } - document.body.className = name + "-theme"; -} + +await I18n.done; + (async ()=>{ @@ -108,13 +25,7 @@ function setTheme(){ } })() -function getBulkUsers(){ - const json = getBulkInfo(); - for(const thing in json.users){ - json.users[thing] = new Specialuser(json.users[thing]); - } - return json; -} + function trimswitcher(){ const json = getBulkInfo(); const map = new Map(); @@ -148,133 +59,8 @@ function trimswitcher(){ console.log(json); } -function getBulkInfo(){ - return JSON.parse(localStorage.getItem("userinfos") as string); -} -function setDefaults(){ - let userinfos = getBulkInfo(); - if(!userinfos){ - localStorage.setItem( - "userinfos", - JSON.stringify({ - currentuser: null, - users: {}, - preferences: { - theme: "Dark", - notifications: false, - notisound: "three", - }, - }) - ); - userinfos = getBulkInfo(); - } - if(userinfos.users === undefined){ - userinfos.users = {}; - } - if(userinfos.accent_color === undefined){ - userinfos.accent_color = "#3096f7"; - } - document.documentElement.style.setProperty( - "--accent-color", - userinfos.accent_color - ); - if(userinfos.preferences === undefined){ - userinfos.preferences = { - theme: "Dark", - notifications: false, - notisound: "three", - }; - } - if(userinfos.preferences && userinfos.preferences.notisound === undefined){ - console.warn("uhoh") - userinfos.preferences.notisound = "three"; - } - localStorage.setItem("userinfos", JSON.stringify(userinfos)); -} -setDefaults(); -class Specialuser{ - serverurls: { - api: string; - cdn: string; - gateway: string; - wellknown: string; - login: string; - }; - email: string; - token: string; - loggedin; - json; - constructor(json: any){ - if(json instanceof Specialuser){ - console.error("specialuser can't construct from another specialuser"); - } - this.serverurls = json.serverurls; - let apistring = new URL(json.serverurls.api).toString(); - apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; - this.serverurls.api = apistring; - this.serverurls.cdn = new URL(json.serverurls.cdn) - .toString() - .replace(/\/$/, ""); - this.serverurls.gateway = new URL(json.serverurls.gateway) - .toString() - .replace(/\/$/, ""); - this.serverurls.wellknown = new URL(json.serverurls.wellknown) - .toString() - .replace(/\/$/, ""); - this.serverurls.login = new URL(json.serverurls.login) - .toString() - .replace(/\/$/, ""); - this.email = json.email; - this.token = json.token; - this.loggedin = json.loggedin; - this.json = json; - this.json.localuserStore ??= {}; - if(!this.serverurls || !this.email || !this.token){ - console.error( - "There are fundamentally missing pieces of info missing from this user" - ); - } - } - set pfpsrc(e){ - this.json.pfpsrc = e; - this.updateLocal(); - } - get pfpsrc(){ - return this.json.pfpsrc; - } - set username(e){ - this.json.username = e; - this.updateLocal(); - } - get username(){ - return this.json.username; - } - set localuserStore(e){ - this.json.localuserStore = e; - this.updateLocal(); - } - 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; - } - toJSON(){ - return this.json; - } - updateLocal(){ - const info = getBulkInfo(); - info.users[this.uid] = this.toJSON(); - localStorage.setItem("userinfos", JSON.stringify(info)); - } -} + + function adduser(user: typeof Specialuser.prototype.json){ user = new Specialuser(user); const info = getBulkInfo(); @@ -286,167 +72,8 @@ function adduser(user: typeof Specialuser.prototype.json){ const instancein = document.getElementById("instancein") as HTMLInputElement; let timeout: ReturnType | string | number | undefined | null = null; // let instanceinfo; -const stringURLMap = new Map(); -const stringURLsMap = new Map< - string, - { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; - } - >(); -async function getapiurls(str: string): Promise< - { - api: string; - cdn: string; - gateway: string; - wellknown: string; - login: string; - } - | false - >{ - function appendApi(str:string){ - return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api"); - } - if(!URL.canParse(str)){ - const val = stringURLMap.get(str); - if(stringURLMap.size===0){ - await new Promise(res=>{ - setInterval(()=>{ - if(stringURLMap.size!==0){ - res(); - } - },100); - }); - } - if(val){ - str = val; - }else{ - const val = stringURLsMap.get(str); - if(val){ - const responce = await fetch( - val.api + (val.api.endsWith("/") ? "" : "/") + "ping" - ); - if(responce.ok){ - if(val.login){ - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - }else{ - val.login = val.api; - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } - } - } - } - } - if(str.at(-1) !== "/"){ - str += "/"; - } - let api: string; - try{ - const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json() - ); - api = info.api; - }catch{ - api=str; - } - if(!URL.canParse(api)){ - return false; - } - const url = new URL(api); - try{ - const info = await fetch( - `${api}${ - url.pathname.includes("api") ? "" : "api" - }/policies/instance/domains` - ).then(x=>x.json()); - const apiurl = new URL(info.apiEndpoint); - return{ - api: info.apiEndpoint+appendApi(apiurl.pathname), - gateway: info.gateway, - cdn: info.cdn, - wellknown: str, - login: info.apiEndpoint+appendApi(apiurl.pathname), - }; - }catch{ - const val = stringURLsMap.get(str); - if(val){ - const responce = await fetch( - val.api + (val.api.endsWith("/") ? "" : "/") + "ping" - ); - if(responce.ok){ - if(val.login){ - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - }else{ - val.login = val.api; - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } - } - } - return false; - } -} -async function checkInstance(instance?: string){ - await instancefetch; - const verify = document.getElementById("verify"); - try{ - verify!.textContent = I18n.getTranslation("login.checking"); - const instanceValue = instance || (instancein as HTMLInputElement).value; - const instanceinfo = (await getapiurls(instanceValue)) as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - value: string; - }; - if(instanceinfo){ - instanceinfo.value = instanceValue; - localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); - verify!.textContent = I18n.getTranslation("login.allGood"); - // @ts-ignore - if(checkInstance.alt){ - // @ts-ignore - checkInstance.alt(); - } - setTimeout((_: any)=>{ - console.log(verify!.textContent); - verify!.textContent = ""; - }, 3000); - }else{ - verify!.textContent = I18n.getTranslation("login.invalid"); - } - }catch{ - console.log("catch"); - verify!.textContent = I18n.getTranslation("login.invalid"); - } -} + if(instancein){ console.log(instancein); @@ -456,7 +83,7 @@ if(instancein){ if(timeout !== null && typeof timeout !== "string"){ clearTimeout(timeout); } - timeout = setTimeout(()=>checkInstance(), 1000); + timeout = setTimeout(()=>checkInstance((instancein as HTMLInputElement).value), 1000); }); if(localStorage.getItem("instanceinfo")){ const json = JSON.parse(localStorage.getItem("instanceinfo")!); @@ -597,51 +224,6 @@ if(document.getElementById("form")){ 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|undefined; - if (registration.installing) { - serviceWorker = registration.installing; - console.log("installing"); - } else if (registration.waiting) { - serviceWorker = registration.waiting; - console.log("waiting"); - } else if (registration.active) { - 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", (_) => { - console.log(serviceWorker.state); - }); - } - }) -} const switchurl = document.getElementById("switch") as HTMLAreaElement; if(switchurl){ @@ -653,22 +235,13 @@ if(switchurl){ checkInstance(""); } } -export{ checkInstance }; trimswitcher(); + export{ - mobile, - iOS, - getBulkUsers, - getBulkInfo, - setTheme, - Specialuser, - getapiurls, adduser, }; -export function getInstances(){ - return instances; -} + diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 7a9a7add..d13cc3ce 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -10,7 +10,7 @@ import{ File }from"./file.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, messagejson }from"./jsontypes.js"; import{ Emoji }from"./emoji.js"; -import{ mobile }from"./login.js"; +import{ mobile }from"./utils/utils.js"; import { I18n } from "./i18n.js"; import { Hover } from "./hover.js"; import { Dialog } from "./settings.js"; diff --git a/src/webpage/oauth2/auth.ts b/src/webpage/oauth2/auth.ts index 2345f157..a7a5d411 100644 --- a/src/webpage/oauth2/auth.ts +++ b/src/webpage/oauth2/auth.ts @@ -1,5 +1,6 @@ import { I18n } from "../i18n.js"; -import{ getBulkUsers, Specialuser, getapiurls }from"../login.js"; +import{ getapiurls }from"../utils/utils.js"; +import { getBulkUsers, Specialuser } from "../utils/utils.js"; import { Permissions } from "../permissions.js"; type botjsonfetch={ guilds:{ diff --git a/src/webpage/register.ts b/src/webpage/register.ts index 7a9555cb..bdba6ed8 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -1,5 +1,6 @@ import { I18n } from "./i18n.js"; -import{ checkInstance, adduser }from"./login.js"; +import{ checkInstance }from"./utils/utils.js"; +import {adduser} from"./login.js"; import { MarkDown } from "./markdown.js"; await I18n.done const registerElement = document.getElementById("register"); diff --git a/src/webpage/utils/binaryUtils.ts b/src/webpage/utils/binaryUtils.ts new file mode 100644 index 00000000..322f192f --- /dev/null +++ b/src/webpage/utils/binaryUtils.ts @@ -0,0 +1,79 @@ +class BinRead{ + private i = 0; + private view:DataView; + constructor(buffer:ArrayBuffer){ + this.view=new DataView(buffer, 0) + } + read16(){ + const int = this.view.getUint16(this.i); + this.i += 2; + return int; + } + read8(){ + const int = this.view.getUint8(this.i); + this.i += 1; + return int; + } + readString8(){ + return this.readStringNo(this.read8()); + } + readString16(){ + return this.readStringNo(this.read16()); + } + readStringNo(length: number){ + const array = new Uint8Array(length); + for(let i = 0; i < length; i++){ + array[i] = this.read8(); + } + //console.log(array); + return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer); + } +} + +class BinWrite{ + private view: DataView; + private buffer:ArrayBuffer; + private i=0; + constructor(maxSize:number=2**26){ + this.buffer=new ArrayBuffer(maxSize); + this.view=new DataView(this.buffer, 0); + } + write16(numb:number){ + this.view.setUint16(this.i,numb); + this.i+=2; + } + write8(numb:number){ + this.view.setUint8(this.i,numb); + this.i+=1; + } + writeString8(str:string){ + const encode=new TextEncoder().encode(str); + this.write8(encode.length); + for(const thing of encode){ + this.write8(thing); + } + } + writeString16(str:string){ + const encode=new TextEncoder().encode(str); + this.write16(encode.length); + for(const thing of encode){ + this.write8(thing); + } + } + writeStringNo(str:string){ + const encode=new TextEncoder().encode(str); + for(const thing of encode){ + this.write8(thing); + } + } + getBuffer(){ + const buf=new ArrayBuffer(this.i); + const ar1=new Uint8Array(buf); + const ar2=new Uint8Array(this.buffer); + for(let i in ar1){ + ar1[+i]=ar2[+i]; + } + return buf; + } +} +export {BinRead,BinWrite} diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts new file mode 100644 index 00000000..abeb73f1 --- /dev/null +++ b/src/webpage/utils/utils.ts @@ -0,0 +1,436 @@ +import { I18n } from "../i18n.js"; +setTheme(); +export function setTheme() { + let name = localStorage.getItem("theme"); + if (!name) { + localStorage.setItem("theme", "Dark"); + name = "Dark"; + } + document.body.className = name + "-theme"; +} +export function getBulkUsers() { + const json = getBulkInfo(); + for (const thing in json.users) { + json.users[thing] = new Specialuser(json.users[thing]); + } + return json; +} +export function getBulkInfo() { + return JSON.parse(localStorage.getItem("userinfos") as string); +} +export function setDefaults() { + let userinfos = getBulkInfo(); + if (!userinfos) { + localStorage.setItem( + "userinfos", + JSON.stringify({ + currentuser: null, + users: {}, + preferences: { + theme: "Dark", + notifications: false, + notisound: "three", + }, + }) + ); + userinfos = getBulkInfo(); + } + if (userinfos.users === undefined) { + userinfos.users = {}; + } + if (userinfos.accent_color === undefined) { + userinfos.accent_color = "#3096f7"; + } + document.documentElement.style.setProperty( + "--accent-color", + userinfos.accent_color + ); + if (userinfos.preferences === undefined) { + userinfos.preferences = { + theme: "Dark", + notifications: false, + notisound: "three", + }; + } + if (userinfos.preferences && userinfos.preferences.notisound === undefined) { + console.warn("uhoh"); + userinfos.preferences.notisound = "three"; + } + localStorage.setItem("userinfos", JSON.stringify(userinfos)); +} +setDefaults(); +export class Specialuser { + serverurls: { + api: string; + cdn: string; + gateway: string; + wellknown: string; + login: string; + }; + email: string; + token: string; + loggedin; + json; + constructor(json: any) { + if (json instanceof Specialuser) { + console.error("specialuser can't construct from another specialuser"); + } + this.serverurls = json.serverurls; + let apistring = new URL(json.serverurls.api).toString(); + apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; + this.serverurls.api = apistring; + this.serverurls.cdn = new URL(json.serverurls.cdn) + .toString() + .replace(/\/$/, ""); + this.serverurls.gateway = new URL(json.serverurls.gateway) + .toString() + .replace(/\/$/, ""); + this.serverurls.wellknown = new URL(json.serverurls.wellknown) + .toString() + .replace(/\/$/, ""); + this.serverurls.login = new URL(json.serverurls.login) + .toString() + .replace(/\/$/, ""); + this.email = json.email; + this.token = json.token; + this.loggedin = json.loggedin; + this.json = json; + this.json.localuserStore ??= {}; + if (!this.serverurls || !this.email || !this.token) { + console.error( + "There are fundamentally missing pieces of info missing from this user" + ); + } + } + set pfpsrc(e) { + this.json.pfpsrc = e; + this.updateLocal(); + } + get pfpsrc() { + return this.json.pfpsrc; + } + set username(e) { + this.json.username = e; + this.updateLocal(); + } + get username() { + return this.json.username; + } + set localuserStore(e) { + this.json.localuserStore = e; + this.updateLocal(); + } + 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; + } + toJSON() { + return this.json; + } + updateLocal() { + const info = getBulkInfo(); + info.users[this.uid] = this.toJSON(); + localStorage.setItem("userinfos", JSON.stringify(info)); + } +} +const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); +const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); +export{ + mobile, + iOS, +} +let instances: +| { + 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; + }; +}[] +| null; +const datalist = document.getElementById("instances"); +console.warn(datalist); +const instancefetch=fetch("/instances.json") +.then(res=>res.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; + } + }[] + )=>{ + instances = json; + if(datalist){ + console.warn(json); + const instancein = document.getElementById("instancein") as HTMLInputElement; + if(instancein && instancein.value === ""){ + instancein.value = json[0].name; + } + for(const instance of json){ + if(instance.display === false){ + continue; + } + const option = document.createElement("option"); + option.disabled = !instance.online; + option.value = instance.name; + if(instance.url){ + stringURLMap.set(option.value, instance.url); + if(instance.urls){ + stringURLsMap.set(instance.url, instance.urls); + } + }else if(instance.urls){ + stringURLsMap.set(option.value, instance.urls); + }else{ + option.disabled = true; + } + if(instance.description){ + option.label = instance.description; + }else{ + option.label = instance.name; + } + datalist.append(option); + } + checkInstance(""); + } + } +); +const stringURLMap = new Map(); + +const stringURLsMap = new Map< + string, + { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; + } + >(); +export async function getapiurls(str: string): Promise< + { + api: string; + cdn: string; + gateway: string; + wellknown: string; + login: string; + } + | false + >{ + function appendApi(str:string){ + return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api"); + } + if(!URL.canParse(str)){ + const val = stringURLMap.get(str); + if(stringURLMap.size===0){ + await new Promise(res=>{ + setInterval(()=>{ + if(stringURLMap.size!==0){ + res(); + } + },100); + }); + } + if(val){ + str = val; + }else{ + const val = stringURLsMap.get(str); + if(val){ + const responce = await fetch( + val.api + (val.api.endsWith("/") ? "" : "/") + "ping" + ); + if(responce.ok){ + if(val.login){ + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + }else{ + val.login = val.api; + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } + } + } + } + } + if(str.at(-1) !== "/"){ + str += "/"; + } + let api: string; + try{ + const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json() + ); + api = info.api; + }catch{ + api=str; + } + if(!URL.canParse(api)){ + return false; + } + const url = new URL(api); + try{ + const info = await fetch( + `${api}${ + url.pathname.includes("api") ? "" : "api" + }/policies/instance/domains` + ).then(x=>x.json()); + const apiurl = new URL(info.apiEndpoint); + return{ + api: info.apiEndpoint+appendApi(apiurl.pathname), + gateway: info.gateway, + cdn: info.cdn, + wellknown: str, + login: info.apiEndpoint+appendApi(apiurl.pathname), + }; + }catch{ + const val = stringURLsMap.get(str); + if(val){ + const responce = await fetch( + val.api + (val.api.endsWith("/") ? "" : "/") + "ping" + ); + if(responce.ok){ + if(val.login){ + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + }else{ + val.login = val.api; + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } + } + } + return false; + } +} +export async function checkInstance(instance: string){ + await instancefetch; + const verify = document.getElementById("verify"); + try{ + verify!.textContent = I18n.getTranslation("login.checking"); + const instanceValue = instance; + const instanceinfo = (await getapiurls(instanceValue)) as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + value: string; + }; + if(instanceinfo){ + instanceinfo.value = instanceValue; + localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); + verify!.textContent = I18n.getTranslation("login.allGood"); + // @ts-ignore + if(checkInstance.alt){ + // @ts-ignore + checkInstance.alt(); + } + setTimeout((_: any)=>{ + console.log(verify!.textContent); + verify!.textContent = ""; + }, 3000); + }else{ + verify!.textContent = I18n.getTranslation("login.invalid"); + } + }catch{ + console.log("catch"); + verify!.textContent = I18n.getTranslation("login.invalid"); + } +} +export function getInstances(){ + return instances; +} +export 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"}); + } + } +} + +if ("serviceWorker" in navigator){ + navigator.serviceWorker.register("/service.js", { + scope: "/", + }).then((registration) => { + let serviceWorker:ServiceWorker|undefined; + if (registration.installing) { + serviceWorker = registration.installing; + console.log("installing"); + } else if (registration.waiting) { + serviceWorker = registration.waiting; + console.log("waiting"); + } else if (registration.active) { + 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", (_) => { + console.log(serviceWorker.state); + }); + } + }) +} From c16db7155578924f23216cf9d568045f283d5078 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 19:14:12 -0600 Subject: [PATCH 0117/1330] edits to the page --- src/webpage/audio/index.html | 5 +++-- src/webpage/audio/page.ts | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/webpage/audio/index.html b/src/webpage/audio/index.html index 429291ea..0d92d352 100644 --- a/src/webpage/audio/index.html +++ b/src/webpage/audio/index.html @@ -15,8 +15,9 @@ -

Place holder text

+

This will eventually be something

+

I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience

- + diff --git a/src/webpage/audio/page.ts b/src/webpage/audio/page.ts index e69de29b..a37af3fc 100644 --- a/src/webpage/audio/page.ts +++ b/src/webpage/audio/page.ts @@ -0,0 +1,3 @@ +import { setTheme } from "../utils/utils.js"; + +setTheme(); From 7c46e70304a4b3b83a5cf762d6c479ddba6c28b6 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 19:26:43 -0600 Subject: [PATCH 0118/1330] little more text --- src/webpage/audio/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webpage/audio/index.html b/src/webpage/audio/index.html index 0d92d352..75cb8e48 100644 --- a/src/webpage/audio/index.html +++ b/src/webpage/audio/index.html @@ -16,7 +16,9 @@

This will eventually be something

-

I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience

+

I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience.

+

why does this tool need to exist?

+

For size reasons jank does not use normal sound files, so I need to make this whole format to be more adaptable

From 6788c54ad695e6b84a72a3e863bb5793a25417ab Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 30 Nov 2024 22:47:50 -0600 Subject: [PATCH 0119/1330] reorginization and new sound format --- gulpfile.cjs | 1 + src/webpage/audio/audio.md | 37 +++++ src/webpage/audio/audio.ts | 213 +++++--------------------- src/webpage/audio/index.html | 1 + src/webpage/audio/page.ts | 154 +++++++++++++++++++ src/webpage/audio/play.ts | 48 ++++++ src/webpage/audio/sounds.jasf | Bin 0 -> 263 bytes src/webpage/audio/track.ts | 46 ++++++ src/webpage/audio/voice.ts | 246 +++++++++++++++++++++++++++++++ src/webpage/channel.ts | 9 +- src/webpage/localuser.ts | 17 ++- src/webpage/utils/binaryUtils.ts | 9 ++ 12 files changed, 595 insertions(+), 186 deletions(-) create mode 100644 src/webpage/audio/audio.md create mode 100644 src/webpage/audio/play.ts create mode 100644 src/webpage/audio/sounds.jasf create mode 100644 src/webpage/audio/track.ts create mode 100644 src/webpage/audio/voice.ts diff --git a/gulpfile.cjs b/gulpfile.cjs index f317e6fb..681c6556 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -113,6 +113,7 @@ return gulp "src/**/*.webp", "src/**/*.gif", "src/**/*.svg", + "src/**/*.jasf", ],{encoding:false}) .pipe(plumber()) // Prevent pipe breaking caused by errors .pipe(gulp.dest("dist")); diff --git a/src/webpage/audio/audio.md b/src/webpage/audio/audio.md new file mode 100644 index 00000000..10ba3bdb --- /dev/null +++ b/src/webpage/audio/audio.md @@ -0,0 +1,37 @@ +# Jank Audio format +This is a markdown file that will try to describe the jank client audio format in sufficient detail so people will know how this weird custom format works into the future. +This is a byte-aligned format, which uses the sequence jasf in asci as a magic number at the start. + +the next 8 bits will decide how many voices this file has/will provide, if the value is 255 you'll instead have a 16 bit number that follows for how many voices there are, this *should* be unused, but I wouldn't be totally surprised if it did get used. + +then it'll parse for that many voices, which will be formatted like the following: +name:String8; +length:f32; **if this is 0, this is not an custom sound and is instead refering to something else which will be explained later™** + +Given a non-zero length, this will parse the sounds as following: +|instruction | description | +| ---------- | ----------- | +| 000 | read float32 and use as value | +| 001 | read time(it'll always be a value between 0 and 1) | +| 002 | read frequency in hz | +| 003 | the constant PI | +| 004 | Math.sin() on the following sequence | +| 005 | multiplies the next two expressions | +| 006 | adds the next two expressions | +| 007 | divides the first expression by the second | +| 008 | subtracts the second expression by the first | +| 009 | first expression to the power of the second | +| 010 | first expression to the modulo of the second | +| 011 | absolute power of the next expression | +| 012 | round the next expression | +| 013 | Math.cos() on the next expression | +> note +> this is likely to expand in the future as more things are needed, but this is just how it is right now. + +Once you've read all of the sounds in the file, you can move on to parsing the tracks. +This starts out by reading a u16 to find out how many tracks there are, then you'll go on to try and parse that many. + +each track will then read a u16 to find out how long it is, then it'll read bytes as the following. +it'll first read the index(which is either a u8 or u16 depending on if the amount of voices was u8 or u16), which is the index of the voice 1-indexed, then if it's not 0 it'll parse two float32s in this order, the volume then the pitch of the sound, if it was 0 it'll instead read one 32f as a delay in the track. if it's a default sound it'll also read a third 32f for length + +then finally you'll parse the audios which are the complete tracks. you'll first parse a u16 to get how many audios there are, then for each audio you'll first parse a string8 for the name, then a u16 for the length then according to the length you'll go on to parse a u16 to get the track (1-indexed again) where if it's 0 you'll instead add a delay according to the next f32, how many ever times according to the length. diff --git a/src/webpage/audio/audio.ts b/src/webpage/audio/audio.ts index cdc412fe..d69a8c98 100644 --- a/src/webpage/audio/audio.ts +++ b/src/webpage/audio/audio.ts @@ -1,181 +1,34 @@ -class AVoice{ - audioCtx: AudioContext; - info: { wave: string | Function; freq: number }; - playing: boolean; - myArrayBuffer: AudioBuffer; - gainNode: GainNode; - buffer: Float32Array; - source: AudioBufferSourceNode; - constructor(wave: string | Function, freq: number, volume = 1){ - this.audioCtx = new window.AudioContext(); - this.info = { wave, freq }; - this.playing = false; - this.myArrayBuffer = this.audioCtx.createBuffer( - 1, - this.audioCtx.sampleRate, - this.audioCtx.sampleRate - ); - this.gainNode = this.audioCtx.createGain(); - this.gainNode.gain.value = volume; - this.gainNode.connect(this.audioCtx.destination); - this.buffer = this.myArrayBuffer.getChannelData(0); - this.source = this.audioCtx.createBufferSource(); - this.source.buffer = this.myArrayBuffer; - this.source.loop = true; - this.source.start(); - this.updateWave(); - } - get wave(): string | Function{ - return this.info.wave; - } - get freq(): number{ - return this.info.freq; - } - set wave(wave: string | Function){ - this.info.wave = wave; - this.updateWave(); - } - set freq(freq: number){ - this.info.freq = freq; - this.updateWave(); - } - updateWave(): void{ - const func = this.waveFunction(); - for(let i = 0; i < this.buffer.length; i++){ - this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); - } - } - waveFunction(): Function{ - if(typeof this.wave === "function"){ - return this.wave; - } - switch(this.wave){ - case"sin": - return(t: number, freq: number)=>{ - return Math.sin(t * Math.PI * 2 * freq); - }; - case"triangle": - return(t: number, freq: number)=>{ - return Math.abs(((4 * t * freq) % 4) - 2) - 1; - }; - case"sawtooth": - return(t: number, freq: number)=>{ - return((t * freq) % 1) * 2 - 1; - }; - case"square": - return(t: number, freq: number)=>{ - return(t * freq) % 2 < 1 ? 1 : -1; - }; - case"white": - return(_t: number, _freq: number)=>{ - return Math.random() * 2 - 1; - }; - } - return new Function(); - } - play(): void{ - if(this.playing){ - return; - } - this.source.connect(this.gainNode); - this.playing = true; - } - stop(): void{ - if(this.playing){ - this.source.disconnect(); - this.playing = false; - } - } - static noises(noise: string): void{ - switch(noise){ - case"three": { - const voicy = new AVoice("sin", 800); - voicy.play(); - setTimeout(_=>{ - voicy.freq = 1000; - }, 50); - setTimeout(_=>{ - voicy.freq = 1300; - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"zip": { - const voicy = new AVoice((t: number, freq: number)=>{ - return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); - }, 700); - voicy.play(); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"square": { - const voicy = new AVoice("square", 600, 0.4); - voicy.play(); - setTimeout(_=>{ - voicy.freq = 800; - }, 50); - setTimeout(_=>{ - voicy.freq = 1000; - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"beep": { - const voicy = new AVoice("sin", 800); - voicy.play(); - setTimeout(_=>{ - voicy.stop(); - }, 50); - setTimeout(_=>{ - voicy.play(); - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case "join":{ - const voicy = new AVoice("triangle", 600,.1); - voicy.play(); - setTimeout(_=>{ - voicy.freq=800; - }, 75); - setTimeout(_=>{ - voicy.freq=1000; - }, 150); - setTimeout(_=>{ - voicy.stop(); - }, 200); - break; - } - case "leave":{ - const voicy = new AVoice("triangle", 850,.5); - voicy.play(); - setTimeout(_=>{ - voicy.freq=700; - }, 100); - setTimeout(_=>{ - voicy.stop(); - voicy.freq=400; - }, 180); - setTimeout(_=>{ - voicy.play(); - }, 200); - setTimeout(_=>{ - voicy.stop(); - }, 250); - break; - } - } - } - static get sounds(){ - return["three", "zip", "square", "beep"]; - } +import { BinRead } from "../utils/binaryUtils.js"; +import { Track } from "./track.js"; + +export class Audio{ + name:string; + tracks:(Track|number)[]; + constructor(name:string,tracks:(Track|number)[]){ + this.tracks=tracks; + this.name=name; + } + static parse(read:BinRead,trackarr:Track[]):Audio{ + const name=read.readString8(); + const length=read.read16(); + const tracks:(Track|number)[]=[] + for(let i=0;isetTimeout(res,thing)); + } + } + } } -export{ AVoice as AVoice }; diff --git a/src/webpage/audio/index.html b/src/webpage/audio/index.html index 75cb8e48..1d71d8f2 100644 --- a/src/webpage/audio/index.html +++ b/src/webpage/audio/index.html @@ -19,6 +19,7 @@

This will eventually be something

I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience.

why does this tool need to exist?

For size reasons jank does not use normal sound files, so I need to make this whole format to be more adaptable

+ diff --git a/src/webpage/audio/page.ts b/src/webpage/audio/page.ts index a37af3fc..ccc5d45a 100644 --- a/src/webpage/audio/page.ts +++ b/src/webpage/audio/page.ts @@ -1,3 +1,157 @@ +import { BinWrite } from "../utils/binaryUtils.js"; import { setTheme } from "../utils/utils.js"; +import { Play } from "./play.js"; setTheme(); +const w=new BinWrite(2**12); +w.writeStringNo("jasf"); +w.write8(4); + +w.writeString8("sin"); +w.write32Float(0); +w.writeString8("triangle"); +w.write32Float(0); +w.writeString8("square"); +w.write32Float(0); + +w.writeString8("custom"); +w.write32Float(150); +//return Math.sin(((t + 2) ** Math.cos(t * 4)) * Math.PI * 2 * freq); +//Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f))) +w.write8(4);//sin +w.write8(5)//times +{ + w.write8(9);//Power + + { + w.write8(6);//adding + w.write8(1);//t + w.write8(0);w.write32Float(2);//2 + } + w.write8(13);//cos + w.write8(5);// times + w.write8(1);//t + w.write8(0);w.write32Float(4);//4 +} +{ + w.write8(5)//times + w.write8(5)//times + w.write8(3);//PI + w.write8(0);w.write32Float(2);//2 + w.write8(2);//freq +} + +w.write16(4);//3 tracks + +w.write16(1);//zip +w.write8(4); +w.write32Float(1) +w.write32Float(700) + +w.write16(3);//beep +{ + w.write8(1); + w.write32Float(1) + w.write32Float(700); + w.write32Float(50); + + w.write8(0); + w.write32Float(100); + + w.write8(1); + w.write32Float(1) + w.write32Float(700); + w.write32Float(50); +} + +w.write16(5);//three +{ + w.write8(1); + w.write32Float(1) + w.write32Float(800); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(1); + w.write32Float(1) + w.write32Float(1000); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(1); + w.write32Float(1) + w.write32Float(1300); + w.write32Float(50); +} + +w.write16(5);//square +{ + w.write8(3); + w.write32Float(1) + w.write32Float(600); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(3); + w.write32Float(1) + w.write32Float(800); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(3); + w.write32Float(1) + w.write32Float(1000); + w.write32Float(50); +} +w.write16(4);//2 audio + +w.writeString8("zip"); +w.write16(1); +w.write16(1); + +w.writeString8("beep"); +w.write16(1); +w.write16(2); + +w.writeString8("three"); +w.write16(1); +w.write16(3); + +w.writeString8("square"); +w.write16(1); +w.write16(4); +const buff=w.getBuffer(); +const play=Play.parseBin(buff); +/* +const zip=play.audios.get("square"); +if(zip){ + setInterval(()=>{ + zip.play() + },1000) + ; + console.log(play.voices[3][0].info.wave) +}; +*/ +console.log(play,buff); + +const download=document.getElementById("download"); +if(download){ + download.onclick=()=>{ + const blob = new Blob([buff], { type: "binary" }); + const downloadUrl = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = downloadUrl; + a.download = "sounds.jasf"; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(downloadUrl); + } +} diff --git a/src/webpage/audio/play.ts b/src/webpage/audio/play.ts new file mode 100644 index 00000000..8ae81f96 --- /dev/null +++ b/src/webpage/audio/play.ts @@ -0,0 +1,48 @@ +import { BinRead } from "../utils/binaryUtils.js"; +import { Track } from "./track.js"; +import { AVoice } from "./voice.js"; +import { Audio } from "./audio.js"; +export class Play{ + voices:[AVoice,string][] + tracks:Track[] + audios:Map; + constructor(voices:[AVoice,string][],tracks:Track[],audios:Map){ + this.voices=voices; + this.tracks=tracks; + this.audios=audios; + } + static parseBin(buffer:ArrayBuffer){ + const read=new BinRead(buffer); + if(read.readStringNo(4)!=="jasf") throw new Error("this is not a jasf file"); + let voices=read.read8(); + let six=false; + if(voices===255){ + voices=read.read16(); + six=true; + } + const voiceArr:[AVoice,string][]=[]; + for(let i=0;i(); + for(let i=0;ir5N^CXdtRN-bv%%b zzJZ)c3chZR7dMzPKH55{+_y2^uIo8KJS`1YlFwM~9$>bkFF-J~6tY=JY)zKcR&4bG yehI6UPzOXP>Hf&G*BQ2Br>yaLqU5_oNiHGV`8$kkwsUR}1*cPRPL$0*G@v)Q`Y9#= literal 0 HcmV?d00001 diff --git a/src/webpage/audio/track.ts b/src/webpage/audio/track.ts new file mode 100644 index 00000000..dac4af31 --- /dev/null +++ b/src/webpage/audio/track.ts @@ -0,0 +1,46 @@ +import { BinRead } from "../utils/binaryUtils.js"; +import { AVoice } from "./voice.js"; + +export class Track{ + seq:(AVoice|number)[]; + constructor(playing:(AVoice|number)[]){ + this.seq=playing; + } + static parse(read:BinRead,play:[AVoice,string][],six:boolean):Track{ + const length=read.read16(); + const play2:(AVoice|number)[]=[]; + for(let i=0;isetTimeout(res,thing)); + } + } + } +} diff --git a/src/webpage/audio/voice.ts b/src/webpage/audio/voice.ts new file mode 100644 index 00000000..703fa126 --- /dev/null +++ b/src/webpage/audio/voice.ts @@ -0,0 +1,246 @@ +import { BinRead } from "../utils/binaryUtils.js"; + +class AVoice{ + audioCtx: AudioContext; + info: { wave: string | Function; freq: number }; + playing: boolean; + myArrayBuffer: AudioBuffer; + gainNode: GainNode; + buffer: Float32Array; + source: AudioBufferSourceNode; + length=1; + constructor(wave: string | Function, freq: number, volume = 1,length=1000){ + this.length=length; + this.audioCtx = new window.AudioContext(); + this.info = { wave, freq }; + this.playing = false; + this.myArrayBuffer = this.audioCtx.createBuffer( + 1, + this.audioCtx.sampleRate*length/1000, + this.audioCtx.sampleRate + ); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value = volume; + this.gainNode.connect(this.audioCtx.destination); + this.buffer = this.myArrayBuffer.getChannelData(0); + this.source = this.audioCtx.createBufferSource(); + this.source.buffer = this.myArrayBuffer; + this.source.loop = true; + this.source.start(); + this.updateWave(); + } + clone(volume:number,freq:number,length=this.length){ + return new AVoice(this.wave,freq,volume,length); + } + get wave(): string | Function{ + return this.info.wave; + } + get freq(): number{ + return this.info.freq; + } + set wave(wave: string | Function){ + this.info.wave = wave; + this.updateWave(); + } + set freq(freq: number){ + this.info.freq = freq; + this.updateWave(); + } + updateWave(): void{ + const func = this.waveFunction(); + for(let i = 0; i < this.buffer.length; i++){ + this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); + } + } + waveFunction(): Function{ + if(typeof this.wave === "function"){ + return this.wave; + } + switch(this.wave){ + case"sin": + return(t: number, freq: number)=>{ + return Math.sin(t * Math.PI * 2 * freq); + }; + case"triangle": + return(t: number, freq: number)=>{ + return Math.abs(((4 * t * freq) % 4) - 2) - 1; + }; + case"sawtooth": + return(t: number, freq: number)=>{ + return((t * freq) % 1) * 2 - 1; + }; + case"square": + return(t: number, freq: number)=>{ + return(t * freq) % 2 < 1 ? 1 : -1; + }; + case"white": + return(_t: number, _freq: number)=>{ + return Math.random() * 2 - 1; + }; + } + return new Function(); + } + play(): void{ + if(this.playing){ + return; + } + this.source.connect(this.gainNode); + this.playing = true; + } + playL(){ + this.play(); + setTimeout(()=>{ + this.stop(); + },this.length); + } + stop(): void{ + if(this.playing){ + this.source.disconnect(); + this.playing = false; + } + } + static noises(noise: string): void{ + switch(noise){ + case"three": { + const voicy = new AVoice("sin", 800); + voicy.play(); + setTimeout(_=>{ + voicy.freq = 1000; + }, 50); + setTimeout(_=>{ + voicy.freq = 1300; + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"zip": { + const voicy = new AVoice((t: number, freq: number)=>{ + return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); + }, 700); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"square": { + const voicy = new AVoice("square", 600, 0.4); + voicy.play(); + setTimeout(_=>{ + voicy.freq = 800; + }, 50); + setTimeout(_=>{ + voicy.freq = 1000; + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"beep": { + const voicy = new AVoice("sin", 800); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + }, 50); + setTimeout(_=>{ + voicy.play(); + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case "join":{ + const voicy = new AVoice("triangle", 600,.1); + voicy.play(); + setTimeout(_=>{ + voicy.freq=800; + }, 75); + setTimeout(_=>{ + voicy.freq=1000; + }, 150); + setTimeout(_=>{ + voicy.stop(); + }, 200); + break; + } + case "leave":{ + const voicy = new AVoice("triangle", 850,.5); + voicy.play(); + setTimeout(_=>{ + voicy.freq=700; + }, 100); + setTimeout(_=>{ + voicy.stop(); + voicy.freq=400; + }, 180); + setTimeout(_=>{ + voicy.play(); + }, 200); + setTimeout(_=>{ + voicy.stop(); + }, 250); + break; + } + } + } + static get sounds(){ + return["three", "zip", "square", "beep"]; + } + static getVoice(read:BinRead):[AVoice,string]{ + const name = read.readString8(); + let length=read.readFloat32(); + let special:Function|string + if(length!==0){ + special=this.parseExpression(read); + }else{ + special=name; + length=1; + } + return [new AVoice(special,0,0,length),name] + } + static parseExpression(read:BinRead):Function{ + return new Function("t","f",`return ${this.PEHelper(read)};`); + } + static PEHelper(read:BinRead):string{ + let state=read.read8(); + switch(state){ + case 0: + return ""+read.readFloat32(); + case 1: + return "t"; + case 2: + return "f"; + case 3: + return `Math.PI` + case 4: + return `Math.sin(${this.PEHelper(read)})`; + case 5: + return `(${this.PEHelper(read)}*${this.PEHelper(read)})`; + case 6: + return `(${this.PEHelper(read)}+${this.PEHelper(read)})`; + case 7: + return `(${this.PEHelper(read)}/${this.PEHelper(read)})`; + case 8: + return `(${this.PEHelper(read)}-${this.PEHelper(read)})`; + case 9: + return `(${this.PEHelper(read)}**${this.PEHelper(read)})`; + case 10: + return `(${this.PEHelper(read)}%${this.PEHelper(read)})`; + case 11: + return `Math.abs(${this.PEHelper(read)})`; + case 12: + return `Math.round(${this.PEHelper(read)})`; + case 13: + return `Math.cos(${this.PEHelper(read)})`; + default: + throw new Error("unexpected case found!"); + + } + } +} + +export{ AVoice as AVoice }; diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 42717deb..feba9584 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1,6 +1,6 @@ "use strict"; import{ Message }from"./message.js"; -import{ AVoice }from"./audio/audio.js"; +import{ AVoice }from"./audio/voice.js"; import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; @@ -1403,7 +1403,12 @@ class Channel extends SnowFlake{ ); } notify(message: Message, deep = 0){ - AVoice.noises(this.localuser.getNotificationSound()); + if(this.localuser.play){ + const voice=this.localuser.play.audios.get(this.localuser.getNotificationSound()); + if(voice){ + voice.play(); + } + } if(!("Notification" in window)){ }else if(Notification.permission === "granted"){ let noticontent: string | undefined | null = message.content.textContent; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index b7b4087c..8d6cf4c7 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1,7 +1,7 @@ import{ Guild }from"./guild.js"; import{ Channel }from"./channel.js"; import{ Direct }from"./direct.js"; -import{ AVoice }from"./audio/audio.js"; +import{ AVoice }from"./audio/voice.js"; import{ User }from"./user.js"; import{ getapiurls, SW }from"./utils/utils.js"; import { getBulkInfo, setTheme, Specialuser } from "./utils/utils.js"; @@ -14,6 +14,7 @@ import { Role } from "./role.js"; import { VoiceFactory } from "./voice.js"; import { I18n, langmap } from "./i18n.js"; import { Emoji } from "./emoji.js"; +import { Play } from "./audio/play.js"; const wsCodesRetry = new Set([4000,4001,4002, 4003, 4005, 4007, 4008, 4009]); @@ -40,6 +41,7 @@ class Localuser{ channelids: Map = new Map(); readonly userMap: Map = new Map(); voiceFactory?:VoiceFactory; + play?:Play; instancePing = { name: "Unknown", }; @@ -51,6 +53,9 @@ class Localuser{ this.userinfo.localuserStore = e; } constructor(userinfo: Specialuser | -1){ + Play.playURL("/audio/sounds.jasf").then((_)=>{ + this.play=_; + }) if(userinfo === -1){ return; } @@ -1234,8 +1239,7 @@ class Localuser{ } { const sounds = AVoice.sounds; - tas - .addSelect( + tas.addSelect( I18n.getTranslation("localuser.notisound"), _=>{ this.setNotificationSound(sounds[_]); @@ -1244,7 +1248,12 @@ class Localuser{ { defaultIndex: sounds.indexOf(this.getNotificationSound()) } ) .watchForChange(_=>{ - AVoice.noises(sounds[_]); + if(this.play){ + const voice=this.play.audios.get(sounds[_]) + if(voice){ + voice.play(); + } + } }); } diff --git a/src/webpage/utils/binaryUtils.ts b/src/webpage/utils/binaryUtils.ts index 322f192f..f2c7c3cb 100644 --- a/src/webpage/utils/binaryUtils.ts +++ b/src/webpage/utils/binaryUtils.ts @@ -20,6 +20,11 @@ class BinRead{ readString16(){ return this.readStringNo(this.read16()); } + readFloat32(){ + const float = this.view.getFloat32(this.i); + this.i += 4; + return float; + } readStringNo(length: number){ const array = new Uint8Array(length); for(let i = 0; i < length; i++){ @@ -38,6 +43,10 @@ class BinWrite{ this.buffer=new ArrayBuffer(maxSize); this.view=new DataView(this.buffer, 0); } + write32Float(numb:number){ + this.view.setFloat32(this.i,numb); + this.i+=4; + } write16(numb:number){ this.view.setUint16(this.i,numb); this.i+=2; From 8ff8ecd59dff761604dd1b6c88a63e353dfb4479 Mon Sep 17 00:00:00 2001 From: MathMan05 <73901602+MathMan05@users.noreply.github.com> Date: Sat, 30 Nov 2024 22:48:42 -0600 Subject: [PATCH 0120/1330] Update audio.md --- src/webpage/audio/audio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/audio/audio.md b/src/webpage/audio/audio.md index 10ba3bdb..afbac10a 100644 --- a/src/webpage/audio/audio.md +++ b/src/webpage/audio/audio.md @@ -12,7 +12,7 @@ Given a non-zero length, this will parse the sounds as following: |instruction | description | | ---------- | ----------- | | 000 | read float32 and use as value | -| 001 | read time(it'll always be a value between 0 and 1) | +| 001 | read time(it'll be the time in seconds) | | 002 | read frequency in hz | | 003 | the constant PI | | 004 | Math.sin() on the following sequence | From 06940fe41f0a78988e92052cd17b7ddd46cf8668 Mon Sep 17 00:00:00 2001 From: MathMan05 <73901602+MathMan05@users.noreply.github.com> Date: Sat, 30 Nov 2024 22:49:10 -0600 Subject: [PATCH 0121/1330] Update audio.md --- src/webpage/audio/audio.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage/audio/audio.md b/src/webpage/audio/audio.md index afbac10a..e97adb83 100644 --- a/src/webpage/audio/audio.md +++ b/src/webpage/audio/audio.md @@ -26,6 +26,7 @@ Given a non-zero length, this will parse the sounds as following: | 012 | round the next expression | | 013 | Math.cos() on the next expression | > note + > this is likely to expand in the future as more things are needed, but this is just how it is right now. Once you've read all of the sounds in the file, you can move on to parsing the tracks. From a22c2f10c09dacd28e7671b443fe35f2f04879db Mon Sep 17 00:00:00 2001 From: MathMan05 <73901602+MathMan05@users.noreply.github.com> Date: Sat, 30 Nov 2024 22:49:22 -0600 Subject: [PATCH 0122/1330] Update audio.md --- src/webpage/audio/audio.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/audio/audio.md b/src/webpage/audio/audio.md index e97adb83..a14e732e 100644 --- a/src/webpage/audio/audio.md +++ b/src/webpage/audio/audio.md @@ -25,8 +25,8 @@ Given a non-zero length, this will parse the sounds as following: | 011 | absolute power of the next expression | | 012 | round the next expression | | 013 | Math.cos() on the next expression | -> note - +> note: +> > this is likely to expand in the future as more things are needed, but this is just how it is right now. Once you've read all of the sounds in the file, you can move on to parsing the tracks. From cc96fd559917e218fc9e2f3f72eddfebfda410fd Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 2 Dec 2024 13:19:56 +0100 Subject: [PATCH 0123/1330] Localisation updates from https://translatewiki.net. --- translations/de.json | 15 +++++++++++++-- translations/ko.json | 19 ++++++++++++++----- translations/ru.json | 31 +++++++++++++++++++++++++++---- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/translations/de.json b/translations/de.json index 80aa4c34..2e0d71cc 100644 --- a/translations/de.json +++ b/translations/de.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "Booky", - "Brettchenweber" + "Brettchenweber", + "SomeRandomDeveloper" ] }, "readableName": "Deutsch", @@ -110,7 +111,12 @@ "serverName": "Name des Servers:", "yesDelete": "Ja, ich bin mir sicher", "noDelete": "Doch nicht", - "loadingDiscovery": "Lade..." + "loadingDiscovery": "Lade...", + "description:": "Beschreibung:", + "systemSelect:": "Kanal für Systemnachrichten:", + "sendrandomwelcome?": "Sende eine zufällige Nachricht, wenn jemand dieser Gilde beitritt", + "helpTips?": "Sende hilfreiche Tipps für deine Gilde!", + "defaultNoti": "Lege die Standardbenachrichtigungseinstellungen deiner Gilde fest!" }, "role": { "color": "Farbe", @@ -120,5 +126,10 @@ "localuser": { "privacyPolcyURL": "Datenschutzerklärung:", "TOSURL": "Nutzungsbedingungen:" + }, + "invite": { + "channel:": "Kanal:", + "inviteMaker": "Einladungsgenerator", + "createInvite": "Einladung erstellen" } } diff --git a/translations/ko.json b/translations/ko.json index 45116978..76710d9a 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -1,6 +1,7 @@ { "@metadata": { "authors": [ + "Suleiman the Magnificent Television", "Ykhwong" ] }, @@ -72,7 +73,7 @@ "nsfw:": "NSFW:", "selectType": "채널 유형 선택", "selectName": "채널 이름", - "selectCatName": "채널 이름", + "selectCatName": "분류 이름", "createChannel": "채널 만들기", "createCatagory": "분류 만들기" }, @@ -114,12 +115,15 @@ "region:": "지역:", "roles": "역할", "all": "모두", - "none": "노드", + "none": "없음", "confirmLeave": "떠나시겠습니까?", "yesLeave": "네, 확실합니다", "serverName": "서버 이름:", "yesDelete": "네, 확실합니다", - "loadingDiscovery": "불러오는 중..." + "loadingDiscovery": "불러오는 중...", + "default": "기본값 ($1)", + "description:": "설명:", + "systemSelect:": "시스템 메시지 채널:" }, "role": { "displaySettings": "표시 설정", @@ -208,14 +212,18 @@ "alreadyJoined": "이미 참여했습니다", "accept": "수락", "longInvitedBy": "$1님이 당신을 $2에 초대했습니다", - "inviteLinkCode": "초대 링크/코드" + "inviteLinkCode": "초대 링크/코드", + "channel:": "채널:", + "createInvite": "초대장 만들기" }, "friends": { "blocked": "차단됨", "blockedusers": "차단된 사용자:", "addfriend": "친구 추가", + "addfriendpromt": "사용자 이름으로 친구 추가:", "notfound": "사용자를 찾을 수 없습니다", "pending": "보류 중", + "pending:": "대기 중인 친구 요청:", "all": "모두", "all:": "모든 친구:", "online": "온라인", @@ -226,7 +234,8 @@ "DMs": { "copyId": "DM ID 복사", "markRead": "읽은 것으로 표시", - "close": "DM 닫기" + "close": "DM 닫기", + "name": "직신" }, "user": { "copyId": "사용자 ID 복사", diff --git a/translations/ru.json b/translations/ru.json index 28f989d7..90c9cb2a 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -10,7 +10,7 @@ "StealthTheAngryBird" ] }, - "readableName": "Английский", + "readableName": "Русский", "reply": "Ответить", "copyrawtext": "Скопировать текст", "copymessageid": "Копировать ID сообщения", @@ -232,7 +232,16 @@ "create": "Создать гильдию", "loadingDiscovery": "Загрузка...", "disoveryTitle": "Путешествие по гильдиям ($1) {{PLURAL:$1|запись|записи|записей}}", - "default": "По умолчанию ($1)" + "emptytitle": "Странное место", + "emptytext": "Вы в странном положении, в этой гильдии нет каналов.", + "default": "По умолчанию ($1)", + "description:": "Описание:", + "systemSelect:": "Канал системных сообщений:", + "sendrandomwelcome?": "Отправлять случайное сообщение, когда кто-то присоединяется к этой гильдии", + "stickWelcomeReact?": "Поощряйте участников вашей гильдии отправлять стикер, когда кто-то присоединяется!", + "boostMessage?": "Отправляйте сообщение, когда кто-то бустит вашу гильдию!", + "helpTips?": "Отправьте полезные советы для вашей гильдии!", + "defaultNoti": "Установите настройки уведомлений по умолчанию для вашей гильдии!" }, "role": { "displaySettings": "Настройки отображения", @@ -346,13 +355,26 @@ "longInvitedBy": "Пользователь $1 пригласил вас на $2", "loginOrCreateAccount": "Войдите или создайте аккаунт ⇌", "joinUsing": "Присоединиться с помощью приглашения", - "inviteLinkCode": "Ссылка-приглашение/Код" + "inviteLinkCode": "Ссылка-приглашение/Код", + "subtext": "до $1 в $2", + "expireAfter": "Истекает через:", + "channel:": "Канал:", + "inviteMaker": "Создатель приглашений", + "createInvite": "Создать приглашение" }, "friends": { + "blocked": "Заблокированные", + "blockedusers": "Заблокированные пользователи:", "addfriend": "Добавить в друзья", "addfriendpromt": "Добавить друзей по имени пользователя:", "notfound": "Пользователь не найден", + "discnotfound": "Дискриминатор не найден:", + "pending": "Ожидающие", + "pending:": "Ожидающиеся заявки в друзья:", + "all": "Все", "all:": "Все друзья:", + "online": "В сети", + "online:": "Друзья в сети:", "friendlist": "Список друзей", "friends": "Друзья" }, @@ -360,7 +382,8 @@ "DMs": { "copyId": "Копировать ID ЛС", "markRead": "Пометить как прочитанное", - "close": "Закрыть ЛС" + "close": "Закрыть ЛС", + "name": "Личные сообщения" }, "user": { "copyId": "Скопировать ID пользователя", From d80d0b24e4e0407a4ad5dbe67b16fcccf48032a0 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 5 Dec 2024 13:20:03 +0100 Subject: [PATCH 0124/1330] Localisation updates from https://translatewiki.net. --- translations/ko.json | 3 ++- translations/lt.json | 3 ++- translations/qqq.json | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/translations/ko.json b/translations/ko.json index 76710d9a..c01599ef 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -1,11 +1,12 @@ { "@metadata": { "authors": [ + "McDutchie", "Suleiman the Magnificent Television", "Ykhwong" ] }, - "readableName": "영어", + "readableName": "한국어", "reply": "답변", "copymessageid": "메시지 ID 복사", "permissions": { diff --git a/translations/lt.json b/translations/lt.json index 2556df8b..9a0ff5a9 100644 --- a/translations/lt.json +++ b/translations/lt.json @@ -1,10 +1,11 @@ { "@metadata": { "authors": [ + "McDutchie", "Nokeoo" ] }, - "readableName": "Anglų", + "readableName": "Lietuvių", "reply": "Atsakyti", "copyrawtext": "Kopijuoti neapdorotą tekstą", "copymessageid": "Kopijuoti žinutės id", diff --git a/translations/qqq.json b/translations/qqq.json index 04e5e26c..dfb94b55 100644 --- a/translations/qqq.json +++ b/translations/qqq.json @@ -4,10 +4,11 @@ "locale": "en", "comment": "Don't know how often I'll update this top part lol", "authors": [ - "MathMan05" + "MathMan05", + "McDutchie" ] }, - "readableName": "This should be the name of the language in the language, please do not translate english into the language for this name", + "readableName": "{{doc-important|This should be the name of the language you are translating into, in that language. Please DO NOT translate this into your language’s word for “English”!}}", "htmlPages": { "box1Items": "this string is slightly atypical, it has a list of items separated by |, please try to keep the same list size as this" }, From 127a9e8250e5285303c2e81199d0e96af1d8edc8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 9 Dec 2024 14:51:24 -0600 Subject: [PATCH 0125/1330] various improvements and fixes fixes a DM bug, and improves mention handling --- src/webpage/channel.ts | 39 ++++++++++++++++++++++++--------------- src/webpage/direct.ts | 24 +++++++++++++++--------- src/webpage/guild.ts | 25 ++++++++++++++++++++----- src/webpage/localuser.ts | 3 +++ src/webpage/message.ts | 6 +++++- src/webpage/style.css | 21 +++++++++++++++++---- 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index feba9584..779ade6b 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -38,7 +38,7 @@ class Channel extends SnowFlake{ position: number = 0; lastreadmessageid: string | undefined; lastmessageid: string | undefined; - mentions!: number; + mentions=0; lastpin!: string; move_id?: string; typing!: number; @@ -518,9 +518,7 @@ class Channel extends SnowFlake{ }; }else{ div.classList.add("channel"); - if(this.hasunreads){ - div.classList.add("cunread"); - } + this.unreads(); Channel.contextmenu.bindContextmenu(div, this,undefined); if(admin){ this.coatDropDiv(div); @@ -624,7 +622,9 @@ class Channel extends SnowFlake{ } } readbottom(){ + this.mentions=0; if(!this.hasunreads){ + this.guild.unreads(); return; } fetch( @@ -637,10 +637,9 @@ class Channel extends SnowFlake{ ); this.lastreadmessageid = this.lastmessageid; this.guild.unreads(); - if(this.myhtml){ - this.myhtml.classList.remove("cunread"); - } + this.unreads(); } + coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false){ div.addEventListener("dragenter", event=>{ console.log("enter"); @@ -1351,6 +1350,20 @@ class Channel extends SnowFlake{ }); } } + unreads(){ + if(!this.hasunreads){ + if(this.myhtml){ + this.myhtml.classList.remove("cunread","mentioned"); + } + }else{ + if(this.myhtml){ + this.myhtml.classList.add("cunread"); + } + if(this.mentions!==0){ + this.myhtml?.classList.add("mentioned") + } + } + } messageCreate(messagep: messageCreateJson): void{ if(!this.hasPermission("VIEW_CHANNEL")){ return; @@ -1361,19 +1374,15 @@ class Channel extends SnowFlake{ this.idToNext.set(this.lastmessageid, messagez.id); this.idToPrev.set(messagez.id, this.lastmessageid); } - + if(messagez.mentionsuser(this.localuser.user)&&messagez.author!==this.localuser.user){ + this.mentions++; + } this.lastmessageid = messagez.id; if(messagez.author === this.localuser.user){ this.lastreadmessageid = messagez.id; - if(this.myhtml){ - this.myhtml.classList.remove("cunread"); - } - }else{ - if(this.myhtml){ - this.myhtml.classList.add("cunread"); - } } + this.unreads(); this.guild.unreads(); if(this === this.localuser.channelfocus){ if(!this.infinitefocus){ diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index d3c03244..3cf2a18e 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -41,6 +41,7 @@ class Direct extends Guild{ const thischannel = new Group(json, this); this.channelids[thischannel.id] = thischannel; this.channels.push(thischannel); + this.localuser.channelids.set(thischannel.id, thischannel); this.sortchannels(); this.printServers(); return thischannel; @@ -282,8 +283,17 @@ class Direct extends Guild{ channelTopic.append(add); } } + get mentions(){ + let mentions=0; + for(const thing of this.localuser.inrelation){ + if(thing.relationshipType===3){ + mentions+=1; + } + } + return mentions; + } giveMember(_member: memberjson){ - console.error("not a real guild, can't give member object"); + throw new Error("not a real guild, can't give member object"); } getRole(/* ID: string */){ return null; @@ -429,6 +439,7 @@ class Group extends Channel{ } messageCreate(messagep: { d: messagejson }){ + this.mentions++; const messagez = new Message(messagep.d, this); if(this.lastmessageid){ this.idToNext.set(this.lastmessageid, messagez.id); @@ -461,20 +472,15 @@ class Group extends Channel{ } this.unreads(); if(messagez.author === this.localuser.user){ + this.mentions=0; return; } - if( - this.localuser.lookingguild?.prevchannel === this && - document.hasFocus() - ){ + if(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) - ){ + }else if(this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)){ this.notify(messagez); } } diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index d50274ea..702ef0aa 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -588,27 +588,42 @@ class Guild extends SnowFlake{ headers: this.headers, }); } + get mentions(){ + let mentions=0; + for(const thing of this.channels){ + mentions+=thing.mentions; + } + return mentions; + } unreads(html?: HTMLElement | undefined){ if(html){ this.html = html; }else{ html = this.html; } + if(!html){ + return; + } let read = true; + let mentions=this.mentions; for(const thing of this.channels){ if(thing.hasunreads){ - console.log(thing); read = false; break; } } - if(!html){ - return; + const noti=html.children[0]; + if(mentions!==0){ + noti.classList.add("pinged"); + noti.textContent=""+mentions; + }else{ + noti.textContent=""; + noti.classList.remove("pinged"); } if(read){ - html.children[0].classList.remove("notiunread"); + noti.classList.remove("notiunread"); }else{ - html.children[0].classList.add("notiunread"); + noti.classList.add("notiunread"); } } getHTML(){ diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 8d6cf4c7..50dcd89a 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -551,6 +551,9 @@ class Localuser{ user.relationshipType = temp.d.type; this.inrelation.add(user); this.relationshipsUpdate(); + const me=this.guildids.get("@me"); + if(!me)break; + me.unreads(); break; } case "RELATIONSHIP_REMOVE":{ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index d13cc3ce..5517a938 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -243,7 +243,11 @@ class Message extends SnowFlake{ if(userd instanceof User){ return this.mentions.includes(userd); }else if(userd instanceof Member){ - return this.mentions.includes(userd.user); + if(this.mentions.includes(userd.user)){ + return true + }else{ + return !new Set(this.mentions).isDisjointFrom(new Set(userd.roles));//if the message mentions a role the user has + } }else{ return false; } diff --git a/src/webpage/style.css b/src/webpage/style.css index 6e94355b..96f3b0a0 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -611,6 +611,10 @@ span.instanceStatus { border-radius: 4px; transition: transform .2s, height .2s; } +.servernoti:hover .unread.pinged { + transform: translate(34px, 14px); + height: 20px; +} .servernoti:hover .unread { transform: translate(-12px, 12px); height: 24px; @@ -618,6 +622,11 @@ span.instanceStatus { .serveropen .unread { transform: translate(-12px, 8px) !important; height: 32px !important; + width: 8px !important; + +} +.serveropen .unread.pinged{ + color: transparent; } .notiunread { transform: translate(-12px, 20px); @@ -625,18 +634,19 @@ span.instanceStatus { #sentdms { position: relative; } -.unread.pinged, .servernoti:hover .unread.pinged { +.unread.pinged{ height: 16px; width: 16px; - transform: translate(28px, 28px); + transform: translate(34px, 34px); background: var(--red); font-size: .75rem; font-weight: bold; line-height: 15px; text-align: center; - border: 4px solid var(--servers-bg); - border-radius: 50%; + /* border: 4px solid var(--servers-bg); */ + /* border-radius: 50%; */ pointer-events: none; + z-index: 10; } /* Channel Panel */ @@ -711,6 +721,9 @@ span.instanceStatus { background: var(--primary-text); border-radius: 50%; } +.cunread.mentioned:after{ + background: var(--red); +} .space { flex: none; height: 1em; From 0ced3f594c06e4db3bb67ca2ae7ba85361ab7695 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 10 Dec 2024 11:44:56 -0600 Subject: [PATCH 0126/1330] bug fix --- src/webpage/channel.ts | 5 ++++- src/webpage/message.ts | 6 ++++++ src/webpage/user.ts | 11 ++++++++++- translations/en.json | 4 +++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 779ade6b..3031b08a 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -761,7 +761,7 @@ class Channel extends SnowFlake{ typebox.classList.remove("typeboxreplying"); } } - async getmessage(id: string): Promise{ + async getmessage(id: string): Promise{ const message = this.messages.get(id); if(message){ return message; @@ -771,6 +771,9 @@ class Channel extends SnowFlake{ { headers: this.headers } ); const json = await gety.json(); + if(json.length===0){ + return undefined; + } return new Message(json[0], this); } } diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 5517a938..540ef402 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -439,6 +439,12 @@ class Message extends SnowFlake{ replyline.classList.add("flexltr","replyflex"); // TODO: Fix this this.channel.getmessage(this.message_reference.message_id).then(message=>{ + if(!message){ + minipfp.remove(); + username.textContent=I18n.getTranslation("message.deleted"); + username.classList.remove("username"); + return; + } if(message.author.relationshipType === 2){ username.textContent = "Blocked user"; return; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 003fe4a7..3126ebde 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -164,7 +164,12 @@ class User extends SnowFlake{ this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){ this.changeRelationship(1); },null,function(){ - return this.relationshipType===0; + return this.relationshipType===0||this.relationshipType===3; + }); + this.contextmenu.addbutton(()=>I18n.getTranslation("friends.removeFriend"), function(this: User){ + this.changeRelationship(0); + },null,function(){ + return this.relationshipType===1; }); this.contextmenu.addbutton( ()=>I18n.getTranslation("user.kick"), @@ -363,11 +368,15 @@ class User extends SnowFlake{ } if(member){ member.bind(html); + }else{ + User.contextmenu.bindContextmenu(html, this, undefined); } }) .catch(err=>{ console.log(err); }); + }else{ + User.contextmenu.bindContextmenu(html, this, undefined); } if(guild){ this.profileclick(html, guild); diff --git a/translations/en.json b/translations/en.json index 3bf4cde3..1a16deed 100644 --- a/translations/en.json +++ b/translations/en.json @@ -322,7 +322,8 @@ "reactionAdd":"Add reaction", "delete":"Delete message", "edit":"Edit message", - "edited":"(edited)" + "edited":"(edited)", + "deleted":"Deleted message" }, "instanceStats":{ "name":"Instance stats: $1", @@ -364,6 +365,7 @@ "blocked":"Blocked", "blockedusers":"Blocked Users:", "addfriend":"Add Friend", + "removeFriend":"Remove Friend", "addfriendpromt":"Add friends by username:", "notfound":"User not found", "discnotfound":"Discriminator not found", From d573a061887289b93c823954f95fe5a61a21f6f8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 10 Dec 2024 11:55:14 -0600 Subject: [PATCH 0127/1330] fix stupid invite URL --- src/webpage/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/home.html b/src/webpage/home.html index 8189617a..34347044 100644 --- a/src/webpage/home.html +++ b/src/webpage/home.html @@ -18,7 +18,7 @@

Jank Client

- Spacebar Guild From 8e7aea193ccc8f8ead7122813ce65e76b12c9eca Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 21:07:59 -0600 Subject: [PATCH 0128/1330] per guild profiles --- src/webpage/channel.ts | 2 +- src/webpage/guild.ts | 7 +- src/webpage/jsontypes.ts | 4 +- src/webpage/localuser.ts | 15 ++- src/webpage/member.ts | 260 ++++++++++++++++++++++++++++++++++++++- src/webpage/user.ts | 165 ++++++++++++++++++++----- translations/en.json | 6 +- 7 files changed, 414 insertions(+), 45 deletions(-) diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 3031b08a..a9baa875 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1440,7 +1440,7 @@ class Channel extends SnowFlake{ } const notification = new Notification(this.notititle(message), { body: noticontent, - icon: message.author.getpfpsrc(), + icon: message.author.getpfpsrc(this.guild), image: imgurl, }); notification.addEventListener("click", _=>{ diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 702ef0aa..dac23be3 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -342,6 +342,7 @@ class Guild extends SnowFlake{ } }); } + this.perminfo ??= { channels: {} }; for(const thing of json.channels){ const temp = new Channel(thing, this); @@ -436,9 +437,9 @@ class Guild extends SnowFlake{ calculateReorder(){ let position = -1; const build: { - id: string; - position: number | undefined; - parent_id: string | undefined; + id: string; + position: number | undefined; + parent_id: string | undefined; }[] = []; for(const thing of this.headchannels){ const thisthing: { diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 767e91b1..45e10d8f 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -141,7 +141,7 @@ type userjson = { premium_since: string; premium_type: number; theme_colors: string; - pronouns: string; + pronouns?: string; badge_ids: string[]; }; type memberjson = { @@ -149,6 +149,8 @@ type memberjson = { id: string; user: userjson | null; guild_id: string; + avatar?:string; + banner?:string; guild: { id: string; } | null; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 50dcd89a..a59fd068 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -90,6 +90,7 @@ class Localuser{ 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; this.lookingguild = undefined; @@ -477,8 +478,10 @@ class Localuser{ temp.d.guild_id ??= "@me"; const channel = this.channelids.get(temp.d.channel_id); if(!channel)break; + const message = channel.messages.get(temp.d.message_id); if(!message)break; + message.reactionRemove(temp.d.emoji, temp.d.user_id); } break; @@ -739,7 +742,7 @@ class Localuser{ for(const member of list){ const memberdiv=document.createElement("div"); - const pfp=await member.user.buildstatuspfp(); + const pfp=await member.user.buildstatuspfp(member); const username=document.createElement("span"); username.classList.add("ellipsis"); username.textContent=member.name; @@ -1094,10 +1097,10 @@ class Localuser{ } } updateProfile(json: { - bio?: string; - pronouns?: string; - accent_color?: number; - }){ + bio?: string; + pronouns?: string; + accent_color?: number; + }){ fetch(this.info.api + "/users/@me/profile", { method: "PATCH", headers: this.headers, @@ -1180,7 +1183,7 @@ class Localuser{ const pronounbox = settingsLeft.addTextInput( I18n.getTranslation("pronouns"), _=>{ - if(newpronouns || newbio || changed){ + if(newpronouns!==undefined||newbio!==undefined||changed!==undefined){ this.updateProfile({ pronouns: newpronouns, bio: newbio, diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 2afb7882..aa4fbaf2 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -4,7 +4,7 @@ import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, presencejson }from"./jsontypes.js"; import { I18n } from "./i18n.js"; -import { Dialog } from "./settings.js"; +import { Dialog, Options } from "./settings.js"; class Member extends SnowFlake{ static already = {}; @@ -12,8 +12,12 @@ class Member extends SnowFlake{ user: User; roles: Role[] = []; nick!: string; - + avatar:void|string=undefined; + banner:void|string=undefined; private constructor(memberjson: memberjson, owner: Guild){ + if(memberjson.id==="1086860370880362328"&&owner.id==="1006649183970562092"){ + console.trace(memberjson); + } super(memberjson.id); this.owner = owner; if(this.localuser.userMap.has(memberjson.id)){ @@ -59,6 +63,251 @@ class Member extends SnowFlake{ this.user.members.delete(this.guild); this.guild.members.delete(this); } + getpfpsrc():string{ + if(this.hypotheticalpfp&&this.avatar){ + return this.avatar; + } + if(this.avatar !== undefined&&this.avatar!==null){ + return`${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${ + this.avatar + }.${this.avatar.startsWith("a_")?"gif":"png"}`; + } + return this.user.getpfpsrc(); + } + getBannerUrl():string|undefined{ + if(this.hypotheticalbanner&&this.banner){ + return this.banner; + } + if(this.banner){ + return `${this.info.cdn}/banners/${this.guild.id}/${ + this.banner + }.${this.banner.startsWith("a_")?"gif":"png"}`;; + }else{ + return undefined; + } + } + joined_at!:string; + premium_since!:string; + deaf!:boolean; + mute!:boolean; + pending!:boolean + clone(){ + return new Member({ + id:this.id+"#clone", + user:this.user.tojson(), + guild_id:this.guild.id, + guild:{id:this.guild.id}, + avatar:this.avatar as (string|undefined), + banner:this.banner as (string|undefined), + //TODO presence + nick:this.nick, + roles:this.roles.map(_=>_.id), + joined_at:this.joined_at, + premium_since:this.premium_since, + deaf:this.deaf, + mute:this.mute, + pending:this.pending + + },this.owner) + } + pronouns?:string; + bio?:string; + hypotheticalpfp=false; + hypotheticalbanner=false; + accent_color?:number; + get headers(){ + return this.owner.headers; + } + + updatepfp(file: Blob): void{ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api + `/guilds/${this.guild.id}/members/${this.id}/`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + avatar: reader.result, + }), + }); + }; + } + updatebanner(file: Blob | null): void{ + if(file){ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: reader.result, + }), + }); + }; + }else{ + fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: null, + }), + }); + } + } + + updateProfile(json: { + bio?: string|null; + pronouns?: string|null; + nick?:string|null; + }){ + console.log(JSON.stringify(json)); + /* + if(json.bio===""){ + json.bio=null; + } + if(json.pronouns===""){ + json.pronouns=null; + } + if(json.nick===""){ + json.nick=null; + } + */ + fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(json), + }); + } + editProfile(options:Options){ + if(this.hasPermission("CHANGE_NICKNAME")){ + const hypotheticalProfile = document.createElement("div"); + let file: undefined | File | null; + let newpronouns: string | undefined; + let newbio: string | undefined; + let nick:string|undefined; + const hypomember = this.clone(); + + let color: string; + async function regen(){ + hypotheticalProfile.textContent = ""; + const hypoprofile = await hypomember.user.buildprofile(-1, -1,hypomember); + + hypotheticalProfile.appendChild(hypoprofile); + } + regen(); + const settingsLeft = options.addOptions(""); + const settingsRight = options.addOptions(""); + settingsRight.addHTMLArea(hypotheticalProfile); + + const nicky=settingsLeft.addTextInput(I18n.getTranslation("member.nick:"),()=>{},{ + initText:this.nick||"" + }); + nicky.watchForChange(_=>{ + hypomember.nick=_; + nick=_; + regen(); + }) + + const finput = settingsLeft.addFileInput( + I18n.getTranslation("uploadPfp"), + _=>{ + if(file){ + this.updatepfp(file); + } + }, + { clear: true } + ); + finput.watchForChange(_=>{ + if(!_){ + file = null; + hypomember.avatar = undefined; + hypomember.hypotheticalpfp = true; + regen(); + return; + } + if(_.length){ + file = _[0]; + const blob = URL.createObjectURL(file); + hypomember.avatar = blob; + hypomember.hypotheticalpfp = true; + regen(); + } + }); + let bfile: undefined | File | null; + const binput = settingsLeft.addFileInput( + I18n.getTranslation("uploadBanner"), + _=>{ + if(bfile !== undefined){ + this.updatebanner(bfile); + } + }, + { clear: true } + ); + binput.watchForChange(_=>{ + if(!_){ + bfile = null; + hypomember.banner = undefined; + hypomember.hypotheticalbanner = true; + regen(); + return; + } + if(_.length){ + bfile = _[0]; + const blob = URL.createObjectURL(bfile); + hypomember.banner = blob; + hypomember.hypotheticalbanner = true; + regen(); + } + }); + let changed = false; + const pronounbox = settingsLeft.addTextInput( + I18n.getTranslation("pronouns"), + _=>{ + if(newpronouns!==undefined||newbio!==undefined||changed!==undefined){ + this.updateProfile({ + pronouns: newpronouns, + bio: newbio, + //accent_color: Number.parseInt("0x" + color.substr(1), 16), + nick + }); + } + }, + { initText: this.pronouns } + ); + pronounbox.watchForChange(_=>{ + hypomember.pronouns = _; + newpronouns = _; + regen(); + }); + const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, { + initText: this.bio, + }); + bioBox.watchForChange(_=>{ + newbio = _; + hypomember.bio = _; + regen(); + }); + return;//Returns early to stop errors + if(this.accent_color){ + color = "#" + this.accent_color.toString(16); + }else{ + color = "transparent"; + } + const colorPicker = settingsLeft.addColorInput( + I18n.getTranslation("profileColor"), + _=>{}, + { initColor: color } + ); + colorPicker.watchForChange(_=>{ + console.log(); + color = _; + hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16); + changed = true; + regen(); + }); + } + } update(memberjson: memberjson){ this.roles=[]; for(const key of Object.keys(memberjson)){ @@ -114,11 +363,16 @@ class Member extends SnowFlake{ owner.members.add(memb); return memb; }else if(memb instanceof Promise){ - return await memb; //I should do something else, though for now this is "good enough" + const member=await memb; //I should do something else, though for now this is "good enough"; + if(member){ + member.update(memberjson); + } + return member; }else{ if(memberjson.presence){ memb.getPresence(memberjson.presence); } + memb.update(memberjson); return memb; } }else{ diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 3126ebde..fa6addb5 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -9,6 +9,7 @@ import { Role } from "./role.js"; import { Search } from "./search.js"; import { I18n } from "./i18n.js"; import { Direct } from "./direct.js"; +import { Settings } from "./settings.js"; class User extends SnowFlake{ owner: Localuser; @@ -19,7 +20,7 @@ class User extends SnowFlake{ relationshipType: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0; bio!: MarkDown; discriminator!: string; - pronouns!: string; + pronouns?: string; bot!: boolean; public_flags!: number; accent_color!: number; @@ -57,11 +58,10 @@ class User extends SnowFlake{ } } - clone(): User{ - return new User( - { + tojson():userjson{ + return { username: this.username, - id: this.id + "#clone", + id: this.id, public_flags: this.public_flags, discriminator: this.discriminator, avatar: this.avatar, @@ -74,7 +74,14 @@ class User extends SnowFlake{ theme_colors: this.theme_colors, pronouns: this.pronouns, badge_ids: this.badge_ids, - }, + } + } + + clone(): User{ + const json=this.tojson(); + json.id+="#clone"; + return new User( + json, this.owner ); } @@ -189,6 +196,20 @@ class User extends SnowFlake{ return us.hasPermission("KICK_MEMBERS") || false; } ); + + this.contextmenu.addbutton( + ()=>I18n.getTranslation("user.editServerProfile"), + function(this: User, member: Member | undefined){ + if(!member) return; + const settings=new Settings(I18n.getTranslation("user.editServerProfile")); + member.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true})); + settings.show(); + }, + null, + member=>{ + return !!member; + } + ); this.contextmenu.addbutton( ()=>I18n.getTranslation("user.ban"), function(this: User, member: Member | undefined){ @@ -319,19 +340,32 @@ class User extends SnowFlake{ } } - buildpfp(): HTMLImageElement{ + buildpfp(guild:Guild|void|Member|null): HTMLImageElement{ const pfp = document.createElement("img"); pfp.loading = "lazy"; pfp.src = this.getpfpsrc(); pfp.classList.add("pfp"); pfp.classList.add("userid:" + this.id); + if(guild){ + (async()=>{ + if(guild instanceof Guild){ + const memb= await Member.resolveMember(this,guild) + if(!memb) return; + pfp.src = memb.getpfpsrc(); + }else{ + pfp.src = guild.getpfpsrc(); + } + + })(); + + } return pfp; } - async buildstatuspfp(): Promise{ + async buildstatuspfp(guild:Guild|void|Member|null): Promise{ const div = document.createElement("div"); div.classList.add("pfpDiv") - const pfp = this.buildpfp(); + const pfp = this.buildpfp(guild); div.append(pfp); const status = document.createElement("div"); status.classList.add("statusDiv"); @@ -423,11 +457,19 @@ class User extends SnowFlake{ } } } - - getpfpsrc(): string{ + /** + * @param guild this is an optional thing that'll get the src of the member if it exists, otherwise ignores it, this is meant to be fast, not accurate + */ + getpfpsrc(guild:Guild|void): string{ if(this.hypotheticalpfp && this.avatar){ return this.avatar; } + if(guild){ + const member=this.members.get(guild) + if(member instanceof Member){ + return member.getpfpsrc(); + } + } if(this.avatar !== null){ return`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ this.avatar @@ -441,12 +483,21 @@ class User extends SnowFlake{ async buildprofile( x: number, y: number, - guild: Guild | null = null + guild: Guild | null | Member = null ): Promise{ if(Contextmenu.currentmenu != ""){ Contextmenu.currentmenu.remove(); } - + const membres=(async ()=>{ + if(!guild) return; + let member:Member|undefined; + if(guild instanceof Guild){ + member=await Member.resolveMember(this,guild) + }else{ + member=guild; + } + return member; + })() const div = document.createElement("div"); if(this.accent_color){ @@ -457,20 +508,18 @@ class User extends SnowFlake{ }else{ div.style.setProperty("--accent_color", "transparent"); } - if(this.banner){ - const banner = document.createElement("img"); - let src: string; - if(!this.hypotheticalbanner){ - src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ - this.banner - }.png`; - }else{ - src = this.banner; + const banner=this.getBanner(guild); + div.append(banner); + membres.then(member=>{ + if(!member) return; + if(member.accent_color&&member.accent_color!==0){ + div.style.setProperty( + "--accent_color", + `#${member.accent_color.toString(16).padStart(6, "0")}` + ); } - banner.src = src; - banner.classList.add("banner"); - div.append(banner); - } + }) + if(x !== -1){ div.style.left = `${x}px`; div.style.top = `${y}px`; @@ -501,7 +550,7 @@ class User extends SnowFlake{ } } })(); - const pfp = await this.buildstatuspfp(); + const pfp = await this.buildstatuspfp(guild); div.appendChild(pfp); const userbody = document.createElement("div"); userbody.classList.add("flexttb","infosection"); @@ -516,16 +565,33 @@ class User extends SnowFlake{ userbody.appendChild(discrimatorhtml); const pronounshtml = document.createElement("p"); - pronounshtml.textContent = this.pronouns; + pronounshtml.textContent = this.pronouns||""; pronounshtml.classList.add("pronouns"); userbody.appendChild(pronounshtml); + membres.then(member=>{ + if(!member) return; + if(member.pronouns&&member.pronouns!==""){ + pronounshtml.textContent=member.pronouns; + } + }); + const rule = document.createElement("hr"); userbody.appendChild(rule); const biohtml = this.bio.makeHTML(); userbody.appendChild(biohtml); + + membres.then(member=>{ + if(!member)return; + if(member.bio&&member.bio!==""){ + //TODO make markdown take Guild + userbody.insertBefore(new MarkDown(member.bio,this.localuser).makeHTML(),biohtml); + biohtml.remove(); + } + }); + if(guild){ - Member.resolveMember(this, guild).then(member=>{ + membres.then(member=>{ if(!member)return; usernamehtml.textContent=member.name; const roles = document.createElement("div"); @@ -556,7 +622,48 @@ class User extends SnowFlake{ } return div; } + getBanner(guild:Guild|null|Member):HTMLImageElement{ + const banner = document.createElement("img"); + const bsrc=this.getBannerUrl(); + if(bsrc){ + banner.src = bsrc; + banner.classList.add("banner"); + } + + if(guild){ + if(guild instanceof Member){ + const bsrc=guild.getBannerUrl(); + if(bsrc){ + banner.src = bsrc; + banner.classList.add("banner"); + } + }else{ + Member.resolveMember(this,guild).then(memb=>{ + if(!memb) return; + const bsrc=memb.getBannerUrl(); + if(bsrc){ + banner.src = bsrc; + banner.classList.add("banner"); + } + }) + } + } + return banner + } + getBannerUrl():string|undefined{ + if(this.banner){ + if(!this.hypotheticalbanner){ + return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ + this.banner + }.png`; + }else{ + return this.banner; + } + }else{ + return undefined; + } + } profileclick(obj: HTMLElement, guild?: Guild): void{ obj.onclick = (e: MouseEvent)=>{ this.buildprofile(e.clientX, e.clientY, guild); diff --git a/translations/en.json b/translations/en.json index 1a16deed..578933ae 100644 --- a/translations/en.json +++ b/translations/en.json @@ -396,7 +396,8 @@ "kick":"Kick member", "ban":"Ban member", "addRole":"Add roles", - "removeRole":"Remove roles" + "removeRole":"Remove roles", + "editServerProfile":"Edit server profile" }, "login":{ "checking":"Checking Instance", @@ -407,7 +408,8 @@ "member":{ "kick":"Kick $1 from $2", "reason:":"Reason:", - "ban":"Ban $1 from $2" + "ban":"Ban $1 from $2", + "nick:":"Nickname:" }, "uploadFilesText":"Upload your files here!", "errorReconnect":"Unable to connect to the server, retrying in **$1** seconds...", From a0e4888806c7ef113ef0b65a2ee8f72dd9247b0d Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 21:21:11 -0600 Subject: [PATCH 0129/1330] tweaks --- src/webpage/guild.ts | 7 +++++++ src/webpage/member.ts | 7 ++++++- src/webpage/user.ts | 4 +--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index dac23be3..ae9f8618 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -43,6 +43,13 @@ class Guild extends SnowFlake{ this.setnotifcation(); }); + this.contextmenu.addbutton( + ()=>I18n.getTranslation("user.editServerProfile"), + function(){ + this.member.showEditProfile(); + } + ); + Guild.contextmenu.addbutton( ()=>I18n.getTranslation("guild.leave"), function(this: Guild){ diff --git a/src/webpage/member.ts b/src/webpage/member.ts index aa4fbaf2..0a617b10 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -4,7 +4,7 @@ import{ Guild }from"./guild.js"; import{ SnowFlake }from"./snowflake.js"; import{ memberjson, presencejson }from"./jsontypes.js"; import { I18n } from "./i18n.js"; -import { Dialog, Options } from "./settings.js"; +import { Dialog, Options, Settings } from "./settings.js"; class Member extends SnowFlake{ static already = {}; @@ -179,6 +179,11 @@ class Member extends SnowFlake{ body: JSON.stringify(json), }); } + showEditProfile(){ + const settings=new Settings(""); + this.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true})); + settings.show(); + } editProfile(options:Options){ if(this.hasPermission("CHANGE_NICKNAME")){ const hypotheticalProfile = document.createElement("div"); diff --git a/src/webpage/user.ts b/src/webpage/user.ts index fa6addb5..97733d8f 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -201,9 +201,7 @@ class User extends SnowFlake{ ()=>I18n.getTranslation("user.editServerProfile"), function(this: User, member: Member | undefined){ if(!member) return; - const settings=new Settings(I18n.getTranslation("user.editServerProfile")); - member.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true})); - settings.show(); + member.showEditProfile(); }, null, member=>{ From ac0901aa1c478f1fb0121f5b085f432b2e32043a Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 21:44:50 -0600 Subject: [PATCH 0130/1330] fix invite page bugs --- src/webpage/invite.ts | 2 +- src/webpage/service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index fcc053b4..7dccc813 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -42,7 +42,7 @@ import { getBulkUsers, Specialuser } from "./utils/utils.js"; } await I18n.done; if(!joinable.length){ -document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("noAccount"); + document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("htmlPages.noAccount"); } const code = window.location.pathname.split("/")[2]; diff --git a/src/webpage/service.ts b/src/webpage/service.ts index b2bf2881..ea63eff6 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -68,7 +68,7 @@ function toPath(url:string):string{ const path=Url.pathname; if(path.startsWith("/channels")){ html="./index.html" - }else if(path.startsWith("/invite")){ + }else if(path.startsWith("/invite/")){ html="./invite.html" } } From 1f36db3d897b38e051faa1de0e2be84a6aa4a0e0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 22:05:23 -0600 Subject: [PATCH 0131/1330] more fixes! --- src/webpage/member.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 0a617b10..92def5d5 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -314,7 +314,9 @@ class Member extends SnowFlake{ } } update(memberjson: memberjson){ - this.roles=[]; + if(memberjson.roles){ + this.roles=[]; + } for(const key of Object.keys(memberjson)){ if(key === "guild" || key === "owner" || key === "user"){ continue; @@ -326,6 +328,12 @@ class Member extends SnowFlake{ if(!role)continue; this.roles.push(role); } + if(!this.user.bot){ + const everyone=this.guild.roleids.get(this.guild.id); + if(everyone&&(this.roles.indexOf(everyone)===-1)){ + this.roles.push(everyone) + } + } continue; } if(key === "presence"){ From 19076efbb3f00e46496acc4fcaa1aad20b977452 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 22:21:45 -0600 Subject: [PATCH 0132/1330] more updates --- src/webpage/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpage/service.ts b/src/webpage/service.ts index ea63eff6..8b6b6bf7 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -68,7 +68,7 @@ function toPath(url:string):string{ const path=Url.pathname; if(path.startsWith("/channels")){ html="./index.html" - }else if(path.startsWith("/invite/")){ + }else if(path.startsWith("/invite/")||path==="/invite"){ html="./invite.html" } } From ac57c8c8c4780ee7e8b33d1cc3ecf3aef3b00f8b Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 11 Dec 2024 22:53:17 -0600 Subject: [PATCH 0133/1330] delete stupid log --- src/webpage/member.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 92def5d5..6104e3e5 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -15,9 +15,6 @@ class Member extends SnowFlake{ avatar:void|string=undefined; banner:void|string=undefined; private constructor(memberjson: memberjson, owner: Guild){ - if(memberjson.id==="1086860370880362328"&&owner.id==="1006649183970562092"){ - console.trace(memberjson); - } super(memberjson.id); this.owner = owner; if(this.localuser.userMap.has(memberjson.id)){ From 5ac6da20251cf4874795dcb37b141dce7e39e2b5 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 12 Dec 2024 13:24:30 +0100 Subject: [PATCH 0134/1330] Localisation updates from https://translatewiki.net. --- translations/ru.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/translations/ru.json b/translations/ru.json index 90c9cb2a..c58f75ab 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -324,7 +324,8 @@ "reactionAdd": "Добавить реакцию", "delete": "Удалить сообщение", "edit": "Редактировать сообщение", - "edited": "(отредактировано)" + "edited": "(отредактировано)", + "deleted": "Удалённое сообщение" }, "instanceStats": { "name": "Статистика инстанции: $1", @@ -366,6 +367,7 @@ "blocked": "Заблокированные", "blockedusers": "Заблокированные пользователи:", "addfriend": "Добавить в друзья", + "removeFriend": "Удалить из друзей", "addfriendpromt": "Добавить друзей по имени пользователя:", "notfound": "Пользователь не найден", "discnotfound": "Дискриминатор не найден:", From f74a1cf8c5bec69c1fbbea2f4fea03ffd57ae629 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 10:15:07 -0600 Subject: [PATCH 0135/1330] package.json fixes --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 1b889083..8d03653a 100644 --- a/package.json +++ b/package.json @@ -14,34 +14,34 @@ "dependencies": { "compression": "^1.7.4", "express": "^4.19.2", - "gulp-sourcemaps": "^3.0.0", + "gulp-sourcemaps": "^2.6.5", "gulp-swc": "^2.2.0", "rimraf": "^6.0.1" }, "devDependencies": { + "@eslint/js": "^9.10.0", + "@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/parser": "^0.27.0", - "eslint-plugin-html": "^8.1.1", - "@stylistic/eslint-plugin-js": "^2.8.0", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", "@rsbuild/core": "^1.1.4", "@rsbuild/plugin-node-polyfill": "^1.2.0", - "@swc/core": "^1.7.26", - "swc": "^1.0.11", - "@eslint/js": "^9.10.0", - "@html-eslint/eslint-plugin": "^0.25.0", "@stylistic/eslint-plugin": "^2.3.0", + "@stylistic/eslint-plugin-js": "^2.8.0", + "@swc/core": "^1.7.26", "@types/compression": "^1.7.5", "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", "@types/node-fetch": "^2.6.11", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", "eslint": "^8.57.1", + "eslint-plugin-html": "^8.1.1", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-unicorn": "^55.0.0", "gulp": "^5.0.0", "gulp-copy": "^5.0.0", "gulp-plumber": "^1.2.1", "gulp-typescript": "^6.0.0-alpha.1", + "swc": "^1.0.11", "typescript": "^5.6.2", "typescript-eslint": "^8.14.0" } From e776d5032fc77f084f38aef5e9c57302b4b30ac2 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 11:46:00 -0600 Subject: [PATCH 0136/1330] fix issues --- .gitignore | 3 + package-lock.json | 5377 --------------------------------------------- 2 files changed, 3 insertions(+), 5377 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 84887f9a..b4b07622 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,6 @@ uptime.json .dist/ bun.lockb src/webpage/translations/langs.js + +package-lock.json +pnpm-lock.yaml diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 025864cd..00000000 --- a/package-lock.json +++ /dev/null @@ -1,5377 +0,0 @@ -{ - "name": "jankclient", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "jankclient", - "version": "0.1.0", - "license": "GPL-3.0", - "dependencies": { - "@html-eslint/parser": "^0.27.0", - "compression": "^1.7.4", - "express": "^4.19.2", - "node-fetch": "^3.3.2", - "ts-to-jsdoc": "^2.2.0" - }, - "devDependencies": { - "@eslint/js": "^9.10.0", - "@html-eslint/eslint-plugin": "^0.25.0", - "@types/compression": "^1.7.5", - "@types/eslint__js": "^8.42.3", - "@types/express": "^4.17.21", - "@types/node-fetch": "^2.6.11", - "eslint": "^8.57.1", - "gulp": "^5.0.0", - "gulp-copy": "^5.0.0", - "gulp-typescript": "^6.0.0-alpha.1", - "typescript": "^5.6.2", - "typescript-eslint": "^7.18.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", - "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@gulpjs/messages": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", - "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@gulpjs/to-absolute-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", - "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@html-eslint/eslint-plugin": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.25.0.tgz", - "integrity": "sha512-5DlvqO8bbe90cKSfFDuEblyrEnhAdgNTjWxXeUxt/XXC2OuMC8CsxzLZjtK3+0X6yM8m4xcE3fymCPwg7zdcXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@html-eslint/parser": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.27.0.tgz", - "integrity": "sha512-F/A1M0jnDAYoRvJiiSC7pIBD9DAsf4EhbndbvEi81aozD/wI8WWXON50xZPUaGHCI1C+2syTVifxDz8MvDKaQA==", - "license": "MIT", - "dependencies": { - "es-html-parser": "^0.0.9" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@ts-morph/common": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", - "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "minimatch": "^9.0.3", - "mkdirp": "^3.0.1", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/compression": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", - "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.56.11", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", - "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint__js": { - "version": "8.42.3", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", - "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.14.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", - "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/qs": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", - "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-done": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", - "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.4", - "once": "^1.4.0", - "stream-exhaust": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/async-settle": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", - "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/bach": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", - "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "async-settle": "^2.0.0", - "now-and-later": "^3.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/bare-events": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", - "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/copy-props": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", - "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "each-props": "^3.0.0", - "is-plain-object": "^5.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", - "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-html-parser": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/es-html-parser/-/es-html-parser-0.0.9.tgz", - "integrity": "sha512-oniQMi+466VFsDzcdron9Ry/sqUJpDJg1bbDn0jFJKDdxXhwIOYDr4DgBnO5/yPLGj2xv+n5yy4L1Q0vAC5TYQ==" - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/fined": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", - "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0", - "object.pick": "^1.3.0", - "parse-filepath": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/flagged-respawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", - "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", - "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.8", - "streamx": "^2.12.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-stream": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", - "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@gulpjs/to-absolute-glob": "^4.0.0", - "anymatch": "^3.1.3", - "fastq": "^1.13.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "is-negated-glob": "^1.0.0", - "normalize-path": "^3.0.0", - "streamx": "^2.12.5" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-watcher": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", - "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "chokidar": "^3.5.3" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glogg": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", - "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", - "dev": true, - "license": "MIT", - "dependencies": { - "sparkles": "^2.1.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/gulp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", - "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-watcher": "^6.0.0", - "gulp-cli": "^3.0.0", - "undertaker": "^2.0.0", - "vinyl-fs": "^4.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulp-cli": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", - "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@gulpjs/messages": "^1.1.0", - "chalk": "^4.1.2", - "copy-props": "^4.0.0", - "gulplog": "^2.2.0", - "interpret": "^3.1.1", - "liftoff": "^5.0.0", - "mute-stdout": "^2.0.0", - "replace-homedir": "^2.0.0", - "semver-greatest-satisfied-range": "^2.0.0", - "string-width": "^4.2.3", - "v8flags": "^4.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulp-copy": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gulp-copy/-/gulp-copy-5.0.0.tgz", - "integrity": "sha512-XgTPwevIxr5bPITtrq24euacqASBMOy2R30IEUXX1mLsbd/GoOo0AM3T9qjF9L2Yp+muRr/spdRskVUjUCDqkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "plugin-error": "^2.0.1", - "through2": "^2.0.3" - }, - "peerDependencies": { - "gulp": "^4.0.1 || ^5.0.0" - } - }, - "node_modules/gulp-typescript": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", - "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "plugin-error": "^1.0.1", - "source-map": "^0.7.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0", - "vinyl-fs": "^3.0.3" - }, - "engines": { - "node": ">= 8" - }, - "peerDependencies": { - "typescript": "~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.7.0-dev " - } - }, - "node_modules/gulp-typescript/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/gulp-typescript/node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/fs-mkdirp-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-typescript/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/gulp-typescript/node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-typescript/node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "license": "MIT", - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-typescript/node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/plugin-error/node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-typescript/node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/gulp-typescript/node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/to-through/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-typescript/node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "license": "MIT", - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-typescript/node_modules/vinyl-fs/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-typescript/node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulplog": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", - "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "glogg": "^2.2.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/last-run": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", - "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lead": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", - "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/liftoff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.0.tgz", - "integrity": "sha512-a5BQjbCHnB+cy+gsro8lXJ4kZluzOijzJ1UVVfyJYZC+IP2pLv1h4+aysQeKuTmyO8NAqfyQAk4HWaP/HjcKTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^5.0.0", - "fined": "^2.0.0", - "flagged-respawn": "^2.0.0", - "is-plain-object": "^5.0.0", - "rechoir": "^0.8.0", - "resolve": "^1.20.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/mute-stdout": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", - "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", - "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/plugin-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", - "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/plugin-error/node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true, - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true, - "license": "ISC" - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replace-homedir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", - "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", - "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^4.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", - "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "sver": "^1.8.3" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/sparkles": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", - "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-composer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", - "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.13.2" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/streamx": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", - "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", - "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sver": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", - "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "semver": "^6.3.0" - } - }, - "node_modules/sver/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/text-decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", - "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/to-through": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", - "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-morph": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", - "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.22.0", - "code-block-writer": "^12.0.0" - } - }, - "node_modules/ts-to-jsdoc": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-to-jsdoc/-/ts-to-jsdoc-2.2.0.tgz", - "integrity": "sha512-5miE85Iy8Hwo3KU4QpoXxSYbTyA7cUitgUAMZF6cQgvOzRmonNFWbxiYE5JcREqV5uvb0DGT/2BTwemlgyV3UQ==", - "license": "MIT", - "dependencies": { - "arg": "^5.0.1", - "ts-morph": "^21.0.1" - }, - "bin": { - "ts-to-jsdoc": "bin/ts-to-jsdoc" - } - }, - "node_modules/ts-to-jsdoc/node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", - "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "7.18.0", - "@typescript-eslint/parser": "7.18.0", - "@typescript-eslint/utils": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", - "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bach": "^2.0.1", - "fast-levenshtein": "^3.0.0", - "last-run": "^2.0.0", - "undertaker-registry": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/undertaker-registry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", - "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/undertaker/node_modules/fast-levenshtein": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", - "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fastest-levenshtein": "^1.0.7" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8flags": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", - "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/value-or-function": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", - "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-contents": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", - "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^5.0.0", - "vinyl": "^3.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-contents/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-contents/node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", - "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fs-mkdirp-stream": "^2.0.1", - "glob-stream": "^8.0.0", - "graceful-fs": "^4.2.11", - "iconv-lite": "^0.6.3", - "is-valid-glob": "^1.0.0", - "lead": "^4.0.0", - "normalize-path": "3.0.0", - "resolve-options": "^2.0.0", - "stream-composer": "^1.0.2", - "streamx": "^2.14.0", - "to-through": "^3.0.0", - "value-or-function": "^4.0.0", - "vinyl": "^3.0.0", - "vinyl-sourcemap": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-fs/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-fs/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-fs/node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", - "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "convert-source-map": "^2.0.0", - "graceful-fs": "^4.2.10", - "now-and-later": "^3.0.0", - "streamx": "^2.12.5", - "vinyl": "^3.0.0", - "vinyl-contents": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-sourcemap/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} From 53f6807c8d7830a9c3c3be472e1dc913f558b8c9 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 14:31:13 -0600 Subject: [PATCH 0137/1330] fix dep issues --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 8d03653a..c6384660 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@rsbuild/plugin-node-polyfill": "^1.2.0", "@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin-js": "^2.8.0", - "@swc/core": "^1.7.26", + "@swc/core": "^1.10.1", "@types/compression": "^1.7.5", "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", @@ -41,7 +41,6 @@ "gulp-copy": "^5.0.0", "gulp-plumber": "^1.2.1", "gulp-typescript": "^6.0.0-alpha.1", - "swc": "^1.0.11", "typescript": "^5.6.2", "typescript-eslint": "^8.14.0" } From e3f9efdd4b1df0004f109c7a859dad3c87c0de21 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 14:36:35 -0600 Subject: [PATCH 0138/1330] dep fixes --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c6384660..cde064ec 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@eslint/js": "^9.10.0", + "@swc/cli": "^0.5.2", "@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/parser": "^0.27.0", "@rsbuild/core": "^1.1.4", From 9862c2dd1a9a0a54af94228d810476de27ae7a22 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 15:06:25 -0600 Subject: [PATCH 0139/1330] fix dep issues again --- gulpfile.cjs | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index 681c6556..c1a43f4c 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -60,7 +60,7 @@ gulp.task("clean", (cb) => { .pipe(gulp.dest("dist")); } else if(argv.bunswc){ return await new Promise(ret=>{ - exec("bun swc ./src -s -d dist").on('exit', function (code) { + exec("bun swc --strip-leading-paths ./src -s -d ./dist/").on('exit', function (code) { ret(); }); }) diff --git a/package.json b/package.json index cde064ec..6456ecb9 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ }, "devDependencies": { "@eslint/js": "^9.10.0", - "@swc/cli": "^0.5.2", "@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/parser": "^0.27.0", "@rsbuild/core": "^1.1.4", "@rsbuild/plugin-node-polyfill": "^1.2.0", "@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin-js": "^2.8.0", + "@swc/cli": "^0.5.2", "@swc/core": "^1.10.1", "@types/compression": "^1.7.5", "@types/eslint__js": "^8.42.3", From eddb92abe8a6fe2a7d4cd1ad820066a6d681ef4c Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 15:08:49 -0600 Subject: [PATCH 0140/1330] adding emoji support --- src/webpage/guild.ts | 75 ++++++++++++++++++++++++++++++++++++++-- src/webpage/jsontypes.ts | 25 ++++++++++++-- src/webpage/localuser.ts | 9 ++++- src/webpage/message.ts | 2 +- src/webpage/style.css | 26 ++++++++++++++ 5 files changed, 130 insertions(+), 7 deletions(-) diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index ae9f8618..c217cb7a 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -6,9 +6,10 @@ import{ Member }from"./member.js"; import{ Dialog, Options, Settings }from"./settings.js"; import{ Permissions }from"./permissions.js"; import{ SnowFlake }from"./snowflake.js"; -import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson,}from"./jsontypes.js"; +import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson, emojipjson,}from"./jsontypes.js"; import{ User }from"./user.js"; import { I18n } from "./i18n.js"; +import { Emoji } from "./emoji.js"; class Guild extends SnowFlake{ owner!: Localuser; @@ -26,7 +27,7 @@ class Guild extends SnowFlake{ parent_id!: string; member!: Member; html!: HTMLElement; - emojis!: emojijson[]; + emojis!: emojipjson[]; large!: boolean; members=new Set(); static contextmenu = new Contextmenu("guild menu"); @@ -178,6 +179,75 @@ class Guild extends SnowFlake{ s1.options.push( new RoleList(permlist, this, this.updateRolePermissions.bind(this),false) ); + { + const emoji=settings.addButton("Emojis"); + emoji.addButtonInput("","Upload Emoji",()=>{ + const popup=new Dialog("Upload emoji"); + const form=popup.options.addForm("",()=>{ + popup.hide(); + },{ + fetchURL:`${this.info.api}/guilds/${this.id}/emojis`, + method:"POST", + headers:this.headers + }); + form.addFileInput("Image:","image",{required:true}); + form.addTextInput("Name:","name",{required:true}); + popup.show(); + }); + const containdiv=document.createElement("div"); + const genDiv=()=>{ + containdiv.innerHTML=""; + for(const emoji of this.emojis){ + const div=document.createElement("div"); + div.classList.add("flexltr","emojiOption"); + const emojic=new Emoji(emoji,this); + + const text=document.createElement("input"); + text.type="text"; + text.value=emoji.name; + text.addEventListener("change",()=>{ + fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`,{ + method:"PATCH", + headers:this.headers, + body:JSON.stringify({name:text.value}) + }).then(e=>{if(!e.ok)text.value=emoji.name;})//if not ok, undo + }); + + const del=document.createElement("span"); + del.classList.add("svgicon", "svg-x","deleteEmoji"); + del.onclick=()=>{ + const diaolog=new Dialog(""); + diaolog.options.addTitle("Are you sure you want to delete this emoji?"); + const options=diaolog.options.addOptions("",{ltr:true}); + options.addButtonInput("",I18n.getTranslation("yes"),()=>{ + fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`,{ + method:"DELETE", + headers:this.headers + }) + diaolog.hide(); + }); + options.addButtonInput("",I18n.getTranslation("no"),()=>{ + diaolog.hide(); + }) + diaolog.show(); + } + + + div.append(emojic.getHTML(true),":",text,":",del); + + containdiv.append(div); + } + } + this.onEmojiUpdate=()=>{ + if(!document.body.contains(containdiv)){ + this.onEmojiUpdate=()=>{}; + return; + } + genDiv(); + } + genDiv(); + emoji.addHTMLArea(containdiv); + } settings.show(); } makeInviteMenu(options:Options,valid:void|(Channel[])){ @@ -303,6 +373,7 @@ class Guild extends SnowFlake{ this.roles.splice(this.roles.indexOf(role),1); this.roleUpdate(role,-1); } + onEmojiUpdate=(_:emojipjson[])=>{}; constructor( json: guildjson | -1, owner: Localuser, diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 45e10d8f..57b39d36 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -168,14 +168,24 @@ type emojijson = { name: string; id?: string; animated?: boolean; - emoji?:string + emoji?:string; }; +type emojipjson=emojijson&{ + available: boolean, + guild_id:string, + user_id:string, + managed:boolean, + require_colons:boolean, + roles:string[], + groups:null//TODO figure out what this means lol +}; + type guildjson = { application_command_counts: { [key: string]: number }; channels: channeljson[]; data_mode: string; - emojis: emojijson[]; + emojis: emojipjson[]; guild_scheduled_events: []; id: string; large: boolean; @@ -543,6 +553,14 @@ roleCreate | { user:userjson }, s:number +}|{ + op: 0, + t: "GUILD_EMOJIS_UPDATE", + d: { + guild_id: string, + emojis: emojipjson[] + }, + s: number }; @@ -706,5 +724,6 @@ export{ voiceserverupdate, webRTCSocket, sdpback, - opRTC12 + opRTC12, + emojipjson }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index a59fd068..43d5288d 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -5,7 +5,7 @@ import{ AVoice }from"./audio/voice.js"; import{ User }from"./user.js"; import{ getapiurls, SW }from"./utils/utils.js"; import { getBulkInfo, setTheme, Specialuser } from "./utils/utils.js"; -import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; +import{channeljson,emojipjson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; import{ Member }from"./member.js"; import{ Dialog, Form, FormError, Options, Settings }from"./settings.js"; import{ getTextNodeAtPosition, MarkDown }from"./markdown.js"; @@ -589,6 +589,13 @@ class Localuser{ member.remove(); break; } + case "GUILD_EMOJIS_UPDATE":{ + const guild=this.guildids.get(temp.d.guild_id); + if(!guild) break; + guild.emojis=temp.d.emojis; + guild.onEmojiUpdate(guild.emojis); + break; + } default :{ //@ts-ignore console.warn("Unhandled case "+temp.t,temp); diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 540ef402..a65dd2d7 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -468,7 +468,7 @@ class Message extends SnowFlake{ div.appendChild(replyline); } div.appendChild(build); - const messageTypes=new Set([0,19]) + const messageTypes=new Set([0,19]); if(messageTypes.has(this.type) || this.attachments.length !== 0){ const pfpRow = document.createElement("div"); let pfpparent, current; diff --git a/src/webpage/style.css b/src/webpage/style.css index 96f3b0a0..3edfb675 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -2175,6 +2175,8 @@ fieldset input[type="radio"] { } .bigemoji{ width:.6in; + object-fit: contain; + height: .6in; } .friendlyButton{ padding: .07in; @@ -2189,3 +2191,27 @@ fieldset input[type="radio"] { .friendlyButton:hover{ background:black; } +.emojiOption{ + border: solid 1px var(--black); + display:flex; + align-items: center; + padding: .075in; + margin-bottom: .2in; + border-radius: .1in; + background: var(--primary-hover); + position: relative; + input{ + width:2in !important; + height:.3in; + } + .bigemoji{ + padding-right:.5in; + } +} +.deleteEmoji{ + width: .3in; + height: .3in; + position: absolute; + right: .2in; + cursor: pointer; +} From af7d331ac5f2d27a7c286c2251622679d66ffa49 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 13 Dec 2024 22:42:42 -0600 Subject: [PATCH 0141/1330] fix condition --- src/webpage/user.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 97733d8f..51756440 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -204,8 +204,8 @@ class User extends SnowFlake{ member.showEditProfile(); }, null, - member=>{ - return !!member; + function(member){ + return member?.id===this.localuser.user.id; } ); this.contextmenu.addbutton( From e06e6de08524f1ceff67d8e8098ed6abbb90f642 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 14 Dec 2024 19:38:23 -0600 Subject: [PATCH 0142/1330] nsfw channel updates --- src/webpage/channel.ts | 89 +++++++++++++++++++++++------- src/webpage/icons/announcensfw.svg | 1 + src/webpage/icons/channelnsfw.svg | 1 + src/webpage/icons/voicensfw.svg | 1 + src/webpage/style.css | 10 ++++ 5 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 src/webpage/icons/announcensfw.svg create mode 100644 src/webpage/icons/channelnsfw.svg create mode 100644 src/webpage/icons/voicensfw.svg diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index a9baa875..7238c8f2 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -5,7 +5,7 @@ import{ Contextmenu }from"./contextmenu.js"; import{ Guild }from"./guild.js"; import{ Localuser }from"./localuser.js"; import{ Permissions }from"./permissions.js"; -import{ Dialog, Settings }from"./settings.js"; +import{ Dialog, Float, Settings }from"./settings.js"; import{ Role, RoleList }from"./role.js"; import{ InfiniteScroller }from"./infiniteScroller.js"; import{ SnowFlake }from"./snowflake.js"; @@ -534,17 +534,17 @@ class Channel extends SnowFlake{ if(this.type === 0){ const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", "svg-channel"); + decoration.classList.add("space", "svgicon", this.nsfw?"svg-channelnsfw":"svg-channel"); }else if(this.type === 2){ // const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", "svg-voice"); + decoration.classList.add("space", "svgicon", this.nsfw?"svg-voicensfw":"svg-voice"); }else if(this.type === 5){ // const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", "svg-announce"); + decoration.classList.add("space", "svgicon", this.nsfw?"svg-announcensfw":"svg-announce"); }else{ console.log(this.type); } @@ -787,25 +787,50 @@ class Channel extends SnowFlake{ } } static genid: number = 0; - async getHTML(addstate=true){ - const id = ++Channel.genid; - if(this.localuser.channelfocus){ - this.localuser.channelfocus.infinite.delete(); - } - if(this.guild !== this.localuser.lookingguild){ - this.guild.loadGuild(); + nsfwPannel(){ + (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+false; + (document.getElementById("upload") as HTMLElement).style.visibility="hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility="hidden"; + const messages = document.getElementById("channelw") as HTMLDivElement; + const messageContainers = Array.from( + messages.getElementsByClassName("messagecontainer") + ); + for(const thing of messageContainers){ + thing.remove(); } - if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ - this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + const elements = Array.from(messages.getElementsByClassName("scroller")); + for(const elm of elements){ + elm.remove(); + console.warn("rouge element detected and removed"); } - if(this.myhtml){ - this.myhtml.classList.add("viewChannel"); + const div=document.getElementById("sideDiv") as HTMLDivElement; + div.innerHTML=""; + const float=new Float(""); + const options=float.options; + //@ts-ignore weird hack, ik, but the user here does have that information + //TODO make an extention of the user class with these aditional properties + //TODO make a popup for `nsfw_allowed==null` to input age + if(this.localuser.user.nsfw_allowed){ + options.addTitle("This is a NSFW channel, do you wish to proceed?"); + const buttons=options.addOptions("",{ltr:true}); + buttons.addButtonInput("","Yes",()=>{ + this.perminfo.nsfwOk=true; + this.localuser.userinfo.updateLocal(); + this.getHTML(); + }); + buttons.addButtonInput("","No",()=>{ + window.history.back(); + }) + }else{ + options.addTitle("You are not allowed in this channel."); } - this.guild.prevchannel = this; - this.guild.perminfo.prevchannel = this.id; - this.localuser.userinfo.updateLocal(); - this.localuser.channelfocus = this; - const prom = this.infinite.delete(); + const html=float.generateHTML(); + html.classList.add("messagecontainer") + messages.append(html); + + } + async getHTML(addstate=true){ + if(addstate){ history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); } @@ -819,6 +844,30 @@ class Channel extends SnowFlake{ ).makeHTML()); channelTopic.removeAttribute("hidden"); }else channelTopic.setAttribute("hidden", ""); + if(this.guild !== this.localuser.lookingguild){ + this.guild.loadGuild(); + } + if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + if(this.myhtml){ + this.myhtml.classList.add("viewChannel"); + } + const id = ++Channel.genid; + if(this.localuser.channelfocus){ + this.localuser.channelfocus.infinite.delete(); + } + this.guild.prevchannel = this; + this.guild.perminfo.prevchannel = this.id; + this.localuser.userinfo.updateLocal(); + this.localuser.channelfocus = this; + //@ts-ignore another hack + if(this.nsfw&&(!this.perminfo.nsfwOk||!this.localuser.user.nsfw_allowed)){ + this.nsfwPannel(); + return; + } + + const prom = this.infinite.delete(); const loading = document.getElementById("loadingdiv") as HTMLDivElement; Channel.regenLoadingMessages(); diff --git a/src/webpage/icons/announcensfw.svg b/src/webpage/icons/announcensfw.svg new file mode 100644 index 00000000..2e3864b0 --- /dev/null +++ b/src/webpage/icons/announcensfw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/icons/channelnsfw.svg b/src/webpage/icons/channelnsfw.svg new file mode 100644 index 00000000..55258d15 --- /dev/null +++ b/src/webpage/icons/channelnsfw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/icons/voicensfw.svg b/src/webpage/icons/voicensfw.svg new file mode 100644 index 00000000..9ba44ba3 --- /dev/null +++ b/src/webpage/icons/voicensfw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webpage/style.css b/src/webpage/style.css index 3edfb675..ffeaef83 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -269,6 +269,16 @@ textarea { .svg-addfriend{ mask: url(/icons/addfriend.svg); } + +.svg-channelnsfw { + mask: url(/icons/channelnsfw.svg); +} +.svg-announcensfw { + mask: url(/icons/announcensfw.svg); +} +.svg-voicensfw{ + mask: url(/icons/voicensfw.svg); +} .svgicon { display: block; height: 100%; From 30ee3aad6a2a27a45d70f2e3d2864d6b66a72ae8 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sun, 15 Dec 2024 21:26:03 -0600 Subject: [PATCH 0143/1330] target more recent TS --- gulpfile.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.cjs b/gulpfile.cjs index c1a43f4c..7b052331 100644 --- a/gulpfile.cjs +++ b/gulpfile.cjs @@ -60,7 +60,7 @@ gulp.task("clean", (cb) => { .pipe(gulp.dest("dist")); } else if(argv.bunswc){ return await new Promise(ret=>{ - exec("bun swc --strip-leading-paths ./src -s -d ./dist/").on('exit', function (code) { + exec("bun swc --strip-leading-paths ./src -s -d ./dist/ -C jsc.target=es2022").on('exit', function (code) { ret(); }); }) From d32a8ab02bdb0aafa7326c573f7e5d993f304f80 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sun, 15 Dec 2024 22:08:07 -0600 Subject: [PATCH 0144/1330] reaction hover events --- src/webpage/hover.ts | 30 ++++++++++++++++++++++-------- src/webpage/localuser.ts | 19 ++++++++++++++++++- src/webpage/member.ts | 3 +++ src/webpage/message.ts | 37 +++++++++++++++++++++++++++++++------ src/webpage/user.ts | 20 +++++++++++++++++++- 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts index fcd5982a..aa02bbd1 100644 --- a/src/webpage/hover.ts +++ b/src/webpage/hover.ts @@ -2,29 +2,43 @@ import { Contextmenu } from "./contextmenu.js"; import { MarkDown } from "./markdown.js"; class Hover{ - str:string|MarkDown - constructor(txt:string|MarkDown){ + str:string|MarkDown|(()=>Promise|MarkDown|string); + constructor(txt:string|MarkDown|(()=>Promise|MarkDown|string)){ this.str=txt; } addEvent(elm:HTMLElement){ let timeOut=setTimeout(()=>{},0); let elm2=document.createElement("div"); elm.addEventListener("mouseover",()=>{ - timeOut=setTimeout(()=>{ - elm2=this.makeHover(elm); + timeOut=setTimeout(async ()=>{ + elm2=await this.makeHover(elm); },750) }); elm.addEventListener("mouseout",()=>{ clearTimeout(timeOut); elm2.remove(); - }) + }); + new MutationObserver(function (e) { + if (e[0].removedNodes) { + clearTimeout(timeOut); + elm2.remove(); + }; + }).observe(elm,{ childList: true }); } - makeHover(elm:HTMLElement){ + async makeHover(elm:HTMLElement){ + if(!document.contains(elm)) return document.createDocumentFragment() as unknown as HTMLDivElement; const div=document.createElement("div"); if(this.str instanceof MarkDown){ - div.append(this.str.makeHTML({stdsize:true})) + div.append(this.str.makeHTML()) + }else if(this.str instanceof Function){ + const hover=await this.str(); + if(hover instanceof MarkDown){ + div.append(hover.makeHTML()); + }else{ + div.innerText=hover; + } }else{ - div.append(this.str); + div.innerText=this.str; } const box=elm.getBoundingClientRect(); div.style.top=(box.bottom+4)+"px"; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 43d5288d..5e953e3b 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1473,7 +1473,7 @@ class Localuser{ localStorage.setItem("Voice enabled","true") }else{ - box.value=true; + box.value=false; const checkbox=box.input.deref(); if(checkbox){ checkbox.checked=false; @@ -1483,6 +1483,23 @@ class Localuser{ localStorage.removeItem("Voice enabled"); } } + const box2=security.addCheckboxInput("Enable logging of bad stuff",()=>{},{initState:Boolean(localStorage.getItem("logbad"))}); + box2.onchange=(e)=>{ + if(e){ + if(confirm("this is meant for spacebar devs")){ + localStorage.setItem("logbad","true") + + }else{ + box2.value=false; + const checkbox=box2.input.deref(); + if(checkbox){ + checkbox.checked=false; + } + } + }else{ + localStorage.removeItem("logbad"); + } + } } }; genSecurity(); diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 6104e3e5..d08be76c 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -359,6 +359,9 @@ class Member extends SnowFlake{ ): Promise{ let user: User; if(owner.localuser.userMap.has(memberjson.id)){ + if(memberjson.user){ + new User(memberjson.user, owner.localuser); + } user = owner.localuser.userMap.get(memberjson.id) as User; }else if(memberjson.user){ user = new User(memberjson.user, owner.localuser); diff --git a/src/webpage/message.ts b/src/webpage/message.ts index a65dd2d7..27d632bd 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -8,7 +8,7 @@ import{ Localuser }from"./localuser.js"; import{ Role }from"./role.js"; import{ File }from"./file.js"; import{ SnowFlake }from"./snowflake.js"; -import{ memberjson, messagejson }from"./jsontypes.js"; +import{ memberjson, messagejson, userjson }from"./jsontypes.js"; import{ Emoji }from"./emoji.js"; import{ mobile }from"./utils/utils.js"; import { I18n } from "./i18n.js"; @@ -725,17 +725,42 @@ class Message extends SnowFlake{ } let emoji: HTMLElement; if(thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)){ - if(/\d{17,21}/.test(thing.emoji.name)) + if(/\d{17,21}/.test(thing.emoji.name)){ thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug - const emo = new Emoji( - thing.emoji as { name: string; id: string; animated: boolean }, - this.guild - ); + } + const emo = new Emoji(thing.emoji as { name: string; id: string; animated: boolean },this.guild); emoji = emo.getHTML(false); }else{ emoji = document.createElement("p"); emoji.textContent = thing.emoji.name; } + const h=new Hover(async ()=>{ + //TODO this can't be real, name conflicts must happen, but for now it's fine + const f=await fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${thing.emoji.name}?limit=3&type=0`,{headers:this.headers}); + const json=await f.json() as userjson[]; + let build=""; + let users=json.map(_=>new User(_,this.localuser)); + //FIXME this is a spacebar bug, I can't fix this the api ignores limit and just sends everything. + users=users.splice(0,3); + let first=true; + for(const user of users){ + if(!first){ + build+=", "; + } + build+=user.name; + first=false; + } + if(thing.count>3){ + build+=", and more!" + }else{ + + } + build+="\nReacted with "+thing.emoji.name; + console.log(build); + return build; + + }); + h.addEvent(reaction); const count = document.createElement("p"); count.textContent = "" + thing.count; count.classList.add("reactionCount"); diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 51756440..6401446d 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -38,6 +38,9 @@ class User extends SnowFlake{ constructor(userjson: userjson, owner: Localuser, dontclone = false){ super(userjson.id); this.owner = owner; + if(localStorage.getItem("logbad")&&owner.user&&owner.user.id!==userjson.id){ + this.checkfortmi(userjson) + } if(!owner){ console.error("missing localuser"); } @@ -57,7 +60,22 @@ class User extends SnowFlake{ return User.checkuser(userjson, owner); } } - + /** + * function is meant to check if userjson contains too much information IE non-public stuff + * + * + */ + checkfortmi(json:any){ + if(json.data){ + console.error("Server sent *way* too much info, this is really bad, it sent data") + } + const bad=new Set(["fingerprints", "extended_settings", "mfa_enabled", "nsfw_allowed", "premium_usage_flags", "totp_last_ticket", "totp_secret", "webauthn_enabled"]); + for(const thing of bad){ + if(json.hasOwnProperty(thing)){ + console.error(thing+" should not be exposed to the client"); + } + } + } tojson():userjson{ return { username: this.username, From 161181f8de5e6d3ff6e373d515f683a28ad2db74 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 16 Dec 2024 13:21:09 +0100 Subject: [PATCH 0145/1330] Localisation updates from https://translatewiki.net. --- translations/ko.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/translations/ko.json b/translations/ko.json index c01599ef..fc65a243 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -187,7 +187,8 @@ "reactionAdd": "반응 추가", "delete": "메시지 삭제", "edit": "메시지 편집", - "edited": "(편집됨)" + "edited": "(편집됨)", + "deleted": "삭제된 메시지" }, "instanceStats": { "name": "인스턴스 통계: $1", @@ -221,6 +222,7 @@ "blocked": "차단됨", "blockedusers": "차단된 사용자:", "addfriend": "친구 추가", + "removeFriend": "친구 제거", "addfriendpromt": "사용자 이름으로 친구 추가:", "notfound": "사용자를 찾을 수 없습니다", "pending": "보류 중", @@ -247,14 +249,16 @@ "unblock": "사용자 차단 해제", "friendReq": "친구 요청", "addRole": "역할 추가", - "removeRole": "역할 제거" + "removeRole": "역할 제거", + "editServerProfile": "서버 프로필 편집" }, "login": { "checking": "인스턴스 확인 중", "invalid": "유효하지 않은 인스턴스입니다. 다시 시도하세요" }, "member": { - "reason:": "이유:" + "reason:": "이유:", + "nick:": "별명:" }, "uploadFilesText": "여기에 파일을 올리세요!", "retrying": "다시 시도하는 중..." From 4458308d98da69b4d8d5fe7c5f3009d73b443b0b Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 19 Dec 2024 13:22:04 +0100 Subject: [PATCH 0146/1330] Localisation updates from https://translatewiki.net. --- translations/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/translations/ru.json b/translations/ru.json index c58f75ab..96dd8af3 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -398,7 +398,8 @@ "kick": "Выгнать участника", "ban": "Забанить участника", "addRole": "Добавить роли", - "removeRole": "Убрать роли" + "removeRole": "Убрать роли", + "editServerProfile": "Редактировать профиль сервера" }, "login": { "checking": "Проверка инстанции", From ffe21e6d6c5771a781c35340c475ba2a0b4b37a3 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 20 Dec 2024 18:59:40 -0600 Subject: [PATCH 0147/1330] search updates --- package.json | 1 + src/webpage/channel.ts | 12 +++++++- src/webpage/index.html | 1 + src/webpage/index.ts | 21 ++++++++++++++ src/webpage/infiniteScroller.ts | 1 - src/webpage/localuser.ts | 49 ++++++++++++++++++++++++++++++- src/webpage/markdown.ts | 51 ++++++++++++++++++++++----------- src/webpage/message.ts | 47 +++++++++++++++++------------- src/webpage/style.css | 44 ++++++++++++++++++++++++++-- src/webpage/user.ts | 1 - 10 files changed, 185 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 6456ecb9..bf586bab 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "express": "^4.19.2", "gulp-sourcemaps": "^2.6.5", "gulp-swc": "^2.2.0", + "prettier": "^3.4.2", "rimraf": "^6.0.1" }, "devDependencies": { diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 7238c8f2..9b0d4295 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -777,6 +777,14 @@ class Channel extends SnowFlake{ return new Message(json[0], this); } } + async focus(id:string){ + console.time() + console.log(await this.getmessage(id)); + await this.getHTML(); + console.timeEnd() + console.warn(id); + this.infinite.focus(id); + } editLast(){ let message:Message|undefined=this.lastmessage; while(message&&message.author!==this.localuser.user){ @@ -1180,7 +1188,7 @@ class Channel extends SnowFlake{ } async buildmessages(){ this.infinitefocus = false; - this.tryfocusinfinate(); + await this.tryfocusinfinate(); } infinitefocus = false; async tryfocusinfinate(){ @@ -1548,3 +1556,5 @@ class Channel extends SnowFlake{ } Channel.setupcontextmenu(); export{ Channel }; + + diff --git a/src/webpage/index.html b/src/webpage/index.html index 1cf492ec..114e9177 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -60,6 +60,7 @@

Server Name

Channel name + diff --git a/src/webpage/index.ts b/src/webpage/index.ts index b7fa3bb1..0a8abed6 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -204,7 +204,28 @@ import { I18n } from "./i18n.js"; if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); }); markdown.giveBox(typebox); + { + const searchBox = document.getElementById("searchBox") as CustomHTMLDivElement; + const markdown = new MarkDown("", thisUser); + searchBox.markdown = markdown; + + searchBox.addEventListener("keydown", event=>{ + + if(event.key === "Enter") { + event.preventDefault(); + thisUser.mSearch(markdown.rawString) + }; + }); + markdown.giveBox(searchBox); + markdown.setCustomBox((e)=>{ + const span=document.createElement("span"); + span.textContent=e.replace("\n",""); + return span; + }); + + + } const images: Blob[] = []; const imagesHtml: HTMLElement[] = []; diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 5332bb42..ac657243 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -37,7 +37,6 @@ offset: number this.destroyFromID(thing[1]); } this.HTMLElements=[]; - this.div=null; } constructor( getIDFromOffset: InfiniteScroller["getIDFromOffset"], diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 5e953e3b..7282aa66 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -5,7 +5,7 @@ import{ AVoice }from"./audio/voice.js"; import{ User }from"./user.js"; import{ getapiurls, SW }from"./utils/utils.js"; import { getBulkInfo, setTheme, Specialuser } from "./utils/utils.js"; -import{channeljson,emojipjson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; +import{channeljson,emojipjson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,messagejson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js"; import{ Member }from"./member.js"; import{ Dialog, Form, FormError, Options, Settings }from"./settings.js"; import{ getTextNodeAtPosition, MarkDown }from"./markdown.js"; @@ -15,6 +15,7 @@ import { VoiceFactory } from "./voice.js"; import { I18n, langmap } from "./i18n.js"; import { Emoji } from "./emoji.js"; import { Play } from "./audio/play.js"; +import { Message } from "./message.js"; const wsCodesRetry = new Set([4000,4001,4002, 4003, 4005, 4007, 4008, 4009]); @@ -669,8 +670,10 @@ class Localuser{ return channel; // Add this line to return the 'channel' variable } async memberListUpdate(list:memberlistupdatejson|void){ + if(this.searching)return; const div=document.getElementById("sideDiv") as HTMLDivElement; div.innerHTML=""; + div.classList.remove("searchDiv"); const guild=this.lookingguild; if(!guild) return; const channel=this.channelfocus; @@ -832,6 +835,7 @@ class Localuser{ } } loadGuild(id: string,forceReload=false): Guild | undefined{ + this.searching=false; let guild = this.guildids.get(id); if(!guild){ guild = this.guildids.get("@me"); @@ -1968,6 +1972,49 @@ class Localuser{ } box.innerHTML=""; } + searching=false; + mSearch(query:string){ + const p=new URLSearchParams("?"); + this.searching=true; + p.set("content",query.trim()); + fetch(this.info.api+`/guilds/${this.lookingguild?.id}/messages/search/?`+p.toString(),{ + headers:this.headers + }).then(_=>_.json()).then((json:{messages:[messagejson][],total_results:number})=>{ + //FIXME total_results shall be ignored as it's known to be bad, spacebar bug. + const messages=json.messages.map(([m])=>{ + const c=this.channelids.get(m.channel_id); + if(!c) return; + if(c.messages.get(m.id)){ + return c.messages.get(m.id); + } + return new Message(m,c,true); + }).filter(_=>_!==undefined); + const sideDiv= document.getElementById("sideDiv"); + if(!sideDiv)return; + sideDiv.innerHTML=""; + sideDiv.classList.add("searchDiv"); + let channel:Channel|undefined=undefined; + for(const message of messages){ + if(channel!==message.channel){ + channel=message.channel; + const h3=document.createElement("h3"); + h3.textContent=channel.name; + h3.classList.add("channelSTitle") + sideDiv.append(h3); + } + const html=message.buildhtml(undefined,true); + html.addEventListener("click",async ()=>{ + try{ + await message.channel.focus(message.id); + }catch(e){ + console.error(e); + } + }) + sideDiv.append(html) + } + }); + } + keydown:(event:KeyboardEvent)=>unknown=()=>{}; keyup:(event:KeyboardEvent)=>boolean=()=>false; //---------- resolving members code ----------- diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 5072e5c1..0fef6e7d 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -300,11 +300,7 @@ class MarkDown{ } } if( - find === count && -(count != 1 || -txt[j + 1] === " " || -txt[j + 1] === "\n" || -txt[j + 1] === undefined) + find === count &&(count != 1 ||txt[j + 1] === " " ||txt[j + 1] === "\n" ||txt[j + 1] === undefined) ){ appendcurrent(); i = j; @@ -715,16 +711,37 @@ txt[j + 1] === undefined) box.onkeyup(new KeyboardEvent("_")); }; } + customBox?:[(arg1:string)=>HTMLElement,((arg1:HTMLElement)=>string)]; + clearCustom(){ + this.customBox=undefined; + } + setCustomBox(stringToHTML:(arg1:string)=>HTMLElement,HTMLToString=MarkDown.gatherBoxText.bind(MarkDown)){ + this.customBox=[stringToHTML,HTMLToString]; + } boxupdate(offset=0){ const box=this.box.deref(); if(!box) return; - const restore = saveCaretPosition(box,offset); + let restore:undefined|(()=>void); + if(this.customBox){ + restore= saveCaretPosition(box,offset,this.customBox[1]); + }else{ + restore= saveCaretPosition(box,offset) + } box.innerHTML = ""; - box.append(this.makeHTML({ keep: true })); + if(this.customBox){ + box.append(this.customBox[0](this.rawString)); + }else{ + box.append(this.makeHTML({ keep: true })); + } if(restore){ restore(); - const test=saveCaretPosition(box); - if(test) test(); + if(this.customBox){ + const test=saveCaretPosition(box,0,this.customBox[1]); + if(test) test(); + }else{ + const test=saveCaretPosition(box); + if(test) test(); + } } this.onUpdate(text,formatted); } @@ -816,7 +833,7 @@ txt[j + 1] === undefined) //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div let text = ""; let formatted=false; -function saveCaretPosition(context: HTMLElement,offset=0){ +function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.gatherBoxText.bind(MarkDown)){ const selection = window.getSelection() as Selection; if(!selection)return; try{ @@ -837,7 +854,7 @@ function saveCaretPosition(context: HTMLElement,offset=0){ i++; } if(base instanceof HTMLElement){ - baseString=MarkDown.gatherBoxText(base) + baseString=txtLengthFunc(base) }else{ baseString=base.textContent as string; } @@ -855,7 +872,7 @@ function saveCaretPosition(context: HTMLElement,offset=0){ const children=[...context.childNodes]; if(children.length===1&&children[0] instanceof Text){ if(selection.containsNode(context,false)){ - build+=MarkDown.gatherBoxText(context as HTMLElement); + build+=txtLengthFunc(context as HTMLElement); }else if(selection.containsNode(context,true)){ if(context.contains(base)||context===base||base.contains(context)){ build+=baseString; @@ -871,7 +888,7 @@ function saveCaretPosition(context: HTMLElement,offset=0){ if(selection.containsNode(node,false)){ if(node instanceof HTMLElement){ - build+=MarkDown.gatherBoxText(node); + build+=txtLengthFunc(node); }else{ build+=node.textContent; } @@ -892,10 +909,10 @@ function saveCaretPosition(context: HTMLElement,offset=0){ } text=build; let len=build.length+offset; - len=Math.min(len,MarkDown.gatherBoxText(context).length) + len=Math.min(len,txtLengthFunc(context).length) return function restore(){ if(!selection)return; - const pos = getTextNodeAtPosition(context, len); + const pos = getTextNodeAtPosition(context, len,txtLengthFunc); selection.removeAllRanges(); const range = new Range(); range.setStart(pos.node, pos.position); @@ -906,7 +923,7 @@ function saveCaretPosition(context: HTMLElement,offset=0){ } } -function getTextNodeAtPosition(root: Node, index: number):{ +function getTextNodeAtPosition(root: Node, index: number,txtLengthFunc=MarkDown.gatherBoxText.bind(MarkDown)):{ node: Node, position: number, }{ @@ -931,7 +948,7 @@ function getTextNodeAtPosition(root: Node, index: number):{ lastElm=node; let len:number if(node instanceof HTMLElement){ - len=MarkDown.gatherBoxText(node).length; + len=txtLengthFunc(node).length; }else{ len=(node.textContent as string).length } diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 27d632bd..2d5bb5e4 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -101,12 +101,14 @@ class Message extends SnowFlake{ if(prev) prev.generateMessage(); this.generateMessage(undefined,false) } - constructor(messagejson: messagejson, owner: Channel){ + constructor(messagejson: messagejson, owner: Channel,dontStore=false){ super(messagejson.id); this.owner = owner; this.headers = this.owner.headers; this.giveData(messagejson); - this.owner.messages.set(this.id, this); + if(!dontStore){ + this.owner.messages.set(this.id, this); + } } reactionToggle(emoji: string | Emoji){ let remove = false; @@ -184,8 +186,11 @@ class Message extends SnowFlake{ } if(this.div){ this.generateMessage(); + return; + } + if(+this.id>+(this.channel.lastmessageid||"0")){ + func(); } - func(); } canDelete(){ return( @@ -338,15 +343,16 @@ class Message extends SnowFlake{ this.generateMessage(); } } - generateMessage(premessage?: Message | undefined, ignoredblock = false){ - if(!this.div)return; + generateMessage(premessage?: Message | undefined, ignoredblock = false,dupe:false|HTMLDivElement=false){ + const div = dupe||this.div; + if(!div)return; + const editmode=this.channel.editing===this; - if(!premessage){ + if(!premessage&&!dupe){ premessage = this.channel.messages.get( this.channel.idToPrev.get(this.id) as string ); } - const div = this.div; for(const user of this.mentions){ if(user === this.localuser.user){ div.classList.add("mentioned"); @@ -390,14 +396,10 @@ class Message extends SnowFlake{ build.classList.add("blocked", "topMessage"); const span = document.createElement("span"); let count = 1; - let next = this.channel.messages.get( - this.channel.idToNext.get(this.id) as string - ); + let next = this.channel.messages.get(this.channel.idToNext.get(this.id) as string); while(next?.author === this.author){ count++; - next = this.channel.messages.get( - this.channel.idToNext.get(next.id) as string - ); + next = this.channel.messages.get(this.channel.idToNext.get(next.id) as string); } span.textContent = I18n.getTranslation("showBlockedMessages",count+""); build.append(span); @@ -620,12 +622,14 @@ class Message extends SnowFlake{ text.append(time); div.classList.add("topMessage"); } - const reactions = document.createElement("div"); - reactions.classList.add("flexltr", "reactiondiv"); - this.reactdiv = new WeakRef(reactions); - this.updateReactions(); - div.append(reactions); - this.bindButtonEvent(); + if(!dupe){ + const reactions = document.createElement("div"); + reactions.classList.add("flexltr", "reactiondiv"); + this.reactdiv = new WeakRef(reactions); + this.updateReactions(); + div.append(reactions); + this.bindButtonEvent(); + } return div; } bindButtonEvent(){ @@ -829,7 +833,10 @@ class Message extends SnowFlake{ } } } - buildhtml(premessage?: Message | undefined): HTMLElement{ + buildhtml(premessage?: Message | undefined,dupe=false): HTMLElement{ + if(dupe){ + return this.generateMessage(premessage,false,document.createElement("div")) as HTMLElement; + } if(this.div){ console.error(`HTML for ${this.id} already exists, aborting`); return this.div; diff --git a/src/webpage/style.css b/src/webpage/style.css index ffeaef83..380c1b3c 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -61,6 +61,10 @@ body { flex-grow: 1; min-height: 0; } +.channelSTitle{ + margin-top:.2in; + margin-bottom:0; +} p, h1, h2, h3, pre, form { margin: 0; } @@ -957,6 +961,26 @@ span.instanceStatus { display:flex; flex-direction: row; } + +.searchBox:empty { + width:2in; +} +.searchBox { + white-space: nowrap; + height: .075in; + padding: 7px 10px 13px 10px; + background: var(--dock-bg); + border-radius: 4px; + /* overflow-y: auto; */ + display:flex; + flex-direction: row; + width: 3in; + margin: 0 .1in; + overflow: hidden; + margin-left: auto; + flex-shrink: 0; + transition: width .2s; +} .outerTypeBox > span::before { content: "\feff"; } @@ -1385,7 +1409,21 @@ img.bigembedimg { .acceptinvbutton:hover, .acceptinvbutton:disabled { background: color-mix(in hsl, var(--green) 80%, var(--black)); } - +#sideDiv.searchDiv{ + width: 30vw; + .topMessage{ + margin-top:2px; + margin-bottom:10px; + cursor:pointer; + padding:.05in; + border-radius:.075in; + background:#00000020; + } + .topMessage:hover{ + background:#00000050; + + } +} /* Sidebar */ #sideDiv { display: none; @@ -1396,6 +1434,7 @@ img.bigembedimg { overflow-y: auto; box-sizing: border-box; } + .memberList { padding-bottom: 16px; color: var(--primary-text-soft); @@ -1416,8 +1455,9 @@ img.bigembedimg { #memberlisttoggleicon { display: block; padding: 12px 0; - margin-left: auto; + margin-left: 0; cursor: pointer; + flex-grow: 0; } #memberlisttoggleicon span { height: 16px; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 6401446d..475bf0ce 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -9,7 +9,6 @@ import { Role } from "./role.js"; import { Search } from "./search.js"; import { I18n } from "./i18n.js"; import { Direct } from "./direct.js"; -import { Settings } from "./settings.js"; class User extends SnowFlake{ owner: Localuser; From d2d0f45c816f7a6bb0b2c64825d620264b1052e6 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 20 Dec 2024 19:28:08 -0600 Subject: [PATCH 0148/1330] formatting updates --- package.json | 6 + src/index.ts | 152 +-- src/stats.ts | 170 ++- src/utils.ts | 66 +- src/webpage/audio/audio.ts | 64 +- src/webpage/audio/index.html | 43 +- src/webpage/audio/page.ts | 199 +-- src/webpage/audio/play.ts | 88 +- src/webpage/audio/track.ts | 87 +- src/webpage/audio/voice.ts | 301 +++-- src/webpage/channel.ts | 1230 +++++++++--------- src/webpage/contextmenu.ts | 138 +- src/webpage/direct.ts | 457 +++---- src/webpage/disimg.ts | 38 +- src/webpage/embed.ts | 228 ++-- src/webpage/emoji.ts | 169 ++- src/webpage/file.ts | 54 +- src/webpage/guild.ts | 852 ++++++------ src/webpage/home.html | 65 +- src/webpage/home.ts | 119 +- src/webpage/hover.ts | 111 +- src/webpage/i18n.ts | 212 ++- src/webpage/index.html | 53 +- src/webpage/index.ts | 222 ++-- src/webpage/infiniteScroller.ts | 207 ++- src/webpage/invite.html | 33 +- src/webpage/invite.ts | 105 +- src/webpage/jsontypes.ts | 589 +++++---- src/webpage/localuser.ts | 2010 +++++++++++++++-------------- src/webpage/login.html | 50 +- src/webpage/login.ts | 216 ++-- src/webpage/markdown.ts | 817 ++++++------ src/webpage/member.ts | 452 +++---- src/webpage/message.ts | 686 +++++----- src/webpage/oauth2/auth.ts | 342 ++--- src/webpage/oauth2/authorize.html | 33 +- src/webpage/permissions.ts | 81 +- src/webpage/register.html | 63 +- src/webpage/register.ts | 125 +- src/webpage/role.ts | 420 +++--- src/webpage/search.ts | 134 +- src/webpage/service.ts | 127 +- src/webpage/settings.ts | 875 ++++++------- src/webpage/snowflake.ts | 14 +- src/webpage/style.css | 538 ++++---- src/webpage/themes.css | 8 +- src/webpage/user.ts | 601 ++++----- src/webpage/utils/binaryUtils.ts | 170 +-- src/webpage/utils/utils.ts | 600 +++++---- src/webpage/voice.ts | 825 ++++++------ 50 files changed, 7798 insertions(+), 7447 deletions(-) diff --git a/package.json b/package.json index bf586bab..e82819ff 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,12 @@ "prettier": "^3.4.2", "rimraf": "^6.0.1" }, + "prettier":{ + "useTabs":true, + "printWidth":100, + "semi":true, + "bracketSpacing":false + }, "devDependencies": { "@eslint/js": "^9.10.0", "@html-eslint/eslint-plugin": "^0.25.0", diff --git a/src/index.ts b/src/index.ts index 1b6040f8..6b9094e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,176 +1,180 @@ #!/usr/bin/env node -import compression from"compression"; -import express, { Request, Response }from"express"; -import fs from"node:fs/promises"; -import path from"node:path"; -import{ observe, uptime }from"./stats.js"; -import{ getApiUrls, inviteResponse }from"./utils.js"; -import{ fileURLToPath }from"node:url"; +import compression from "compression"; +import express, {Request, Response} from "express"; +import fs from "node:fs/promises"; +import path from "node:path"; +import {observe, uptime} from "./stats.js"; +import {getApiUrls, inviteResponse} from "./utils.js"; +import {fileURLToPath} from "node:url"; import {readFileSync} from "fs"; -import process from"node:process"; +import process from "node:process"; const devmode = (process.env.NODE_ENV || "development") === "development"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface Instance { - name: string; - [key: string]: any; + name: string; + [key: string]: any; } const app = express(); -type instace={ - name:string, - description?:string, - descriptionLong?:string, - image?:string, - url?:string, - language:string, - country:string, - display:boolean, - urls?:{ - wellknown:string, - api:string, - cdn:string, - gateway:string, - login?:string - }, - contactInfo?:{ - discord?:string, - github?:string, - email?:string, - spacebar?:string, - matrix?:string, - mastodon?:string - } -} -const instances=JSON.parse(readFileSync(process.env.JANK_INSTANCES_PATH||(__dirname+"/webpage/instances.json")).toString()) as instace[]; +type instace = { + name: string; + description?: string; + descriptionLong?: string; + image?: string; + url?: string; + language: string; + country: string; + display: boolean; + urls?: { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; + }; + contactInfo?: { + discord?: string; + github?: string; + email?: string; + spacebar?: string; + matrix?: string; + mastodon?: string; + }; +}; +const instances = JSON.parse( + readFileSync(process.env.JANK_INSTANCES_PATH || __dirname + "/webpage/instances.json").toString(), +) as instace[]; const instanceNames = new Map(); -for(const instance of instances){ +for (const instance of instances) { instanceNames.set(instance.name, instance); } app.use(compression()); -async function updateInstances(): Promise{ - try{ - const response = await fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"); +async function updateInstances(): Promise { + try { + const response = await fetch( + "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json", + ); const json = (await response.json()) as Instance[]; - for(const instance of json){ - if(instanceNames.has(instance.name)){ + for (const instance of json) { + if (instanceNames.has(instance.name)) { const existingInstance = instanceNames.get(instance.name); - if(existingInstance){ - for(const key of Object.keys(instance)){ - if(!existingInstance[key]){ + if (existingInstance) { + for (const key of Object.keys(instance)) { + if (!existingInstance[key]) { existingInstance[key] = instance[key]; } } } - }else{ + } else { instances.push(instance as any); } } observe(instances); - }catch(error){ + } catch (error) { console.error("Error updating instances:", error); } } updateInstances(); -app.use("/getupdates", async (_req: Request, res: Response)=>{ - try{ +app.use("/getupdates", async (_req: Request, res: Response) => { + try { const stats = await fs.stat(path.join(__dirname, "webpage")); res.send(stats.mtimeMs.toString()); - }catch(error){ + } catch (error) { console.error("Error getting updates:", error); res.status(500).send("Error getting updates"); } }); -app.use("/services/oembed", (req: Request, res: Response)=>{ +app.use("/services/oembed", (req: Request, res: Response) => { inviteResponse(req, res); }); -app.use("/uptime", (req: Request, res: Response)=>{ +app.use("/uptime", (req: Request, res: Response) => { const instanceUptime = uptime.get(req.query.name as string); res.send(instanceUptime); }); -app.use("/", async (req: Request, res: Response)=>{ +app.use("/", async (req: Request, res: Response) => { const scheme = req.secure ? "https" : "http"; const host = `${scheme}://${req.get("Host")}`; const ref = host + req.originalUrl; - if(host && ref){ + if (host && ref) { const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; res.set( "Link", - `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` + `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`, ); } - if(req.path === "/"){ + if (req.path === "/") { res.sendFile(path.join(__dirname, "webpage", "home.html")); return; } - if(req.path.startsWith("/instances.json")){ + if (req.path.startsWith("/instances.json")) { res.json(instances); return; } - if(req.path.startsWith("/invite/")){ + if (req.path.startsWith("/invite/")) { res.sendFile(path.join(__dirname, "webpage", "invite.html")); return; } const filePath = path.join(__dirname, "webpage", req.path); - try{ + try { await fs.access(filePath); - if(devmode){ + if (devmode) { const filePath2 = path.join(__dirname, "../src/webpage", req.path); - try{ + try { await fs.access(filePath2); res.sendFile(filePath2); return; - }catch{} + } catch {} } res.sendFile(filePath); - }catch{ - try{ + } catch { + try { await fs.access(`${filePath}.html`); - if(devmode){ + if (devmode) { const filePath2 = path.join(__dirname, "../src/webpage", req.path); - try{ + try { await fs.access(filePath2 + ".html"); res.sendFile(filePath2 + ".html"); return; - }catch{} + } catch {} } res.sendFile(`${filePath}.html`); - }catch{ - if(req.path.startsWith("/src/webpage")){ + } catch { + if (req.path.startsWith("/src/webpage")) { const filePath2 = path.join(__dirname, "..", req.path); - try{ + try { await fs.access(filePath2); res.sendFile(filePath2); return; - }catch{} + } catch {} } res.sendFile(path.join(__dirname, "webpage", "index.html")); } } }); -app.set('trust proxy', (ip:string) => ip.startsWith("127.")); +app.set("trust proxy", (ip: string) => ip.startsWith("127.")); const PORT = process.env.PORT || Number(process.argv[2]) || 8080; -app.listen(PORT, ()=>{ +app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); -export{ getApiUrls }; +export {getApiUrls}; diff --git a/src/stats.ts b/src/stats.ts index 1356d30d..d26062b0 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,39 +1,39 @@ -import fs from"node:fs"; -import path from"node:path"; -import{ getApiUrls }from"./utils.js"; -import{ fileURLToPath }from"node:url"; -import{ setTimeout, clearTimeout }from"node:timers"; +import fs from "node:fs"; +import path from "node:path"; +import {getApiUrls} from "./utils.js"; +import {fileURLToPath} from "node:url"; +import {setTimeout, clearTimeout} from "node:timers"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface UptimeEntry { - time: number; - online: boolean; + time: number; + online: boolean; } interface Instance { - name: string; - urls?: { api: string }; - url?: string; - online?: boolean; - uptime?: { - daytime: number; - weektime: number; - alltime: number; - }; + name: string; + urls?: {api: string}; + url?: string; + online?: boolean; + uptime?: { + daytime: number; + weektime: number; + alltime: number; + }; } const uptimeObject: Map = loadUptimeObject(); -export{ uptimeObject as uptime }; +export {uptimeObject as uptime}; -function loadUptimeObject(): Map{ - const filePath = process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"); - if(fs.existsSync(filePath)){ - try{ +function loadUptimeObject(): Map { + const filePath = process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"); + if (fs.existsSync(filePath)) { + try { const data = JSON.parse(fs.readFileSync(filePath, "utf8")); return new Map(Object.entries(data)); - }catch(error){ + } catch (error) { console.error("Error reading uptime.json:", error); return new Map(); } @@ -43,26 +43,26 @@ function loadUptimeObject(): Map{ let saveTimeout: ReturnType | null = null; -function saveUptimeObject(): void{ - if(saveTimeout){ +function saveUptimeObject(): void { + if (saveTimeout) { clearTimeout(saveTimeout); } - saveTimeout = setTimeout(()=>{ + saveTimeout = setTimeout(() => { const data = Object.fromEntries(uptimeObject); fs.writeFile( - process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"), + process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"), JSON.stringify(data), - error=>{ - if(error){ + (error) => { + if (error) { console.error("Error saving uptime.json:", error); } - } + }, ); }, 5000); // Batch updates every 5 seconds } -function removeUndefinedKey(): void{ - if(uptimeObject.has("undefined")){ +function removeUndefinedKey(): void { + if (uptimeObject.has("undefined")) { uptimeObject.delete("undefined"); saveUptimeObject(); } @@ -70,101 +70,89 @@ function removeUndefinedKey(): void{ removeUndefinedKey(); -export async function observe(instances: Instance[]): Promise{ +export async function observe(instances: Instance[]): Promise { const activeInstances = new Set(); - const instancePromises = instances.map(instance=>resolveInstance(instance, activeInstances) - ); + const instancePromises = instances.map((instance) => resolveInstance(instance, activeInstances)); await Promise.allSettled(instancePromises); updateInactiveInstances(activeInstances); } -async function resolveInstance( - instance: Instance, - activeInstances: Set -): Promise{ - try{ +async function resolveInstance(instance: Instance, activeInstances: Set): Promise { + try { calcStats(instance); const api = await getApiUrl(instance); - if(!api){ + if (!api) { handleUnresolvedApi(instance); return; } activeInstances.add(instance.name); await checkHealth(instance, api); scheduleHealthCheck(instance, api); - }catch(error){ + } catch (error) { console.error("Error resolving instance:", error); } } -async function getApiUrl(instance: Instance): Promise{ - if(instance.urls){ +async function getApiUrl(instance: Instance): Promise { + if (instance.urls) { return instance.urls.api; } - if(instance.url){ + if (instance.url) { const urls = await getApiUrls(instance.url); return urls ? urls.api : null; } return null; } -function handleUnresolvedApi(instance: Instance): void{ +function handleUnresolvedApi(instance: Instance): void { setStatus(instance, false); console.warn(`${instance.name} does not resolve api URL`, instance); - setTimeout(()=>resolveInstance(instance, new Set()), 1000 * 60 * 30); + setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30); } -function scheduleHealthCheck(instance: Instance, api: string): void{ +function scheduleHealthCheck(instance: Instance, api: string): void { const checkInterval = 1000 * 60 * 30; const initialDelay = Math.random() * 1000 * 60 * 10; - setTimeout(()=>{ + setTimeout(() => { checkHealth(instance, api); - setInterval(()=>checkHealth(instance, api), checkInterval); + setInterval(() => checkHealth(instance, api), checkInterval); }, initialDelay); } -async function checkHealth( - instance: Instance, - api: string, - tries = 0 -): Promise{ - try{ - const response = await fetch(`${api}/ping`, { method: "HEAD" }); +async function checkHealth(instance: Instance, api: string, tries = 0): Promise { + try { + const response = await fetch(`${api}/ping`, {method: "HEAD"}); console.log(`Checking health for ${instance.name}: ${response.status}`); - if(response.ok || tries > 3){ + if (response.ok || tries > 3) { setStatus(instance, response.ok); - }else{ + } else { retryHealthCheck(instance, api, tries); } - }catch(error){ + } catch (error) { console.error(`Error checking health for ${instance.name}:`, error); - if(tries > 3){ + if (tries > 3) { setStatus(instance, false); - }else{ + } else { retryHealthCheck(instance, api, tries); } } } -function retryHealthCheck( - instance: Instance, - api: string, - tries: number -): void{ - setTimeout(()=>checkHealth(instance, api, tries + 1), 30000); +function retryHealthCheck(instance: Instance, api: string, tries: number): void { + setTimeout(() => checkHealth(instance, api, tries + 1), 30000); } -function updateInactiveInstances(activeInstances: Set): void{ - for(const key of uptimeObject.keys()){ - if(!activeInstances.has(key)){ +function updateInactiveInstances(activeInstances: Set): void { + for (const key of uptimeObject.keys()) { + if (!activeInstances.has(key)) { setStatus(key, false); } } } -function calcStats(instance: Instance): void{ +function calcStats(instance: Instance): void { const obj = uptimeObject.get(instance.name); - if(!obj)return; + if (!obj) return; const now = Date.now(); const day = now - 1000 * 60 * 60 * 24; @@ -176,7 +164,7 @@ function calcStats(instance: Instance): void{ let weektime = 0; let online = false; - for(let i = 0; i < obj.length; i++){ + for (let i = 0; i < obj.length; i++) { const entry = obj[i]; online = entry.online; const stamp = entry.time; @@ -186,11 +174,11 @@ function calcStats(instance: Instance): void{ totalTimePassed += timePassed; alltime += Number(online) * timePassed; - if(stamp + timePassed > week){ + if (stamp + timePassed > week) { const weekTimePassed = Math.min(timePassed, nextStamp - week); weektime += Number(online) * weekTimePassed; - if(stamp + timePassed > day){ + if (stamp + timePassed > day) { const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); daytime += Number(online) * dayTimePassed; } @@ -198,13 +186,7 @@ function calcStats(instance: Instance): void{ } instance.online = online; - instance.uptime = calculateUptimeStats( - totalTimePassed, - alltime, - daytime, - weektime, - online - ); + instance.uptime = calculateUptimeStats(totalTimePassed, alltime, daytime, weektime, online); } function calculateUptimeStats( @@ -212,46 +194,46 @@ function calculateUptimeStats( alltime: number, daytime: number, weektime: number, - online: boolean -): { daytime: number; weektime: number; alltime: number }{ + online: boolean, +): {daytime: number; weektime: number; alltime: number} { const dayInMs = 1000 * 60 * 60 * 24; const weekInMs = dayInMs * 7; alltime /= totalTimePassed; - if(totalTimePassed > dayInMs){ + if (totalTimePassed > dayInMs) { daytime = daytime || (online ? dayInMs : 0); daytime /= dayInMs; - if(totalTimePassed > weekInMs){ + if (totalTimePassed > weekInMs) { weektime = weektime || (online ? weekInMs : 0); weektime /= weekInMs; - }else{ + } else { weektime = alltime; } - }else{ + } else { weektime = alltime; daytime = alltime; } - return{ daytime, weektime, alltime }; + return {daytime, weektime, alltime}; } -function setStatus(instance: string | Instance, status: boolean): void{ +function setStatus(instance: string | Instance, status: boolean): void { const name = typeof instance === "string" ? instance : instance.name; let obj = uptimeObject.get(name); - if(!obj){ + if (!obj) { obj = []; uptimeObject.set(name, obj); } const lastEntry = obj.at(-1); - if(!lastEntry || lastEntry.online !== status){ - obj.push({ time: Date.now(), online: status }); + if (!lastEntry || lastEntry.online !== status) { + obj.push({time: Date.now(), online: status}); saveUptimeObject(); - if(typeof instance !== "string"){ + if (typeof instance !== "string") { calcStats(instance); } } diff --git a/src/utils.ts b/src/utils.ts index 029a13c7..b553fa1d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,74 +1,76 @@ -import{ Request, Response }from"express"; +import {Request, Response} from "express"; interface ApiUrls { - api: string; - gateway: string; - cdn: string; - wellknown: string; + api: string; + gateway: string; + cdn: string; + wellknown: string; } interface Invite { - guild: { - name: string; - description?: string; - icon?: string; - id: string; - }; - inviter?: { - username: string; - }; + guild: { + name: string; + description?: string; + icon?: string; + id: string; + }; + inviter?: { + username: string; + }; } -export async function getApiUrls(url: string): Promise{ - if(!url.endsWith("/")){ +export async function getApiUrls(url: string): Promise { + if (!url.endsWith("/")) { url += "/"; } - try{ - const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json()); + try { + const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then((res) => res.json()); const api = info.api; const apiUrl = new URL(api); const policies: any = await fetch( - `${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains` - ).then(res=>res.json()); - return{ + `${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`, + ).then((res) => res.json()); + return { api: policies.apiEndpoint, gateway: policies.gateway, cdn: policies.cdn, wellknown: url, }; - }catch(error){ + } catch (error) { console.error("Error fetching API URLs:", error); return null; } } -export async function inviteResponse(req: Request, res: Response): Promise{ +export async function inviteResponse(req: Request, res: Response): Promise { let url: URL; - try{ + try { url = new URL(req.query.url as string); - }catch{ + } catch { const scheme = req.secure ? "https" : "http"; const host = `${scheme}://${req.get("Host")}`; url = new URL(host); } - try{ - if(url.pathname.startsWith("invite")){ + try { + if (url.pathname.startsWith("invite")) { throw new Error("Invalid invite URL"); } const code = url.pathname.split("/")[2]; const instance = url.searchParams.get("instance"); - if(!instance){ + if (!instance) { throw new Error("Instance not specified"); } const urls = await getApiUrls(instance); - if(!urls){ + if (!urls) { throw new Error("Failed to get API URLs"); } - const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise); + const invite = await fetch(`${urls.api}/invites/${code}`).then( + (json) => json.json() as Promise, + ); const title = invite.guild.name; const description = invite.inviter ? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}` @@ -84,7 +86,7 @@ export async function inviteResponse(req: Request, res: Response): Promise thumbnail, description, }); - }catch(error){ + } catch (error) { console.error("Error processing invite response:", error); res.json({ type: "link", @@ -95,4 +97,4 @@ export async function inviteResponse(req: Request, res: Response): Promise url: url.toString(), }); } -} \ No newline at end of file +} diff --git a/src/webpage/audio/audio.ts b/src/webpage/audio/audio.ts index d69a8c98..ecebd359 100644 --- a/src/webpage/audio/audio.ts +++ b/src/webpage/audio/audio.ts @@ -1,34 +1,34 @@ -import { BinRead } from "../utils/binaryUtils.js"; -import { Track } from "./track.js"; +import {BinRead} from "../utils/binaryUtils.js"; +import {Track} from "./track.js"; -export class Audio{ - name:string; - tracks:(Track|number)[]; - constructor(name:string,tracks:(Track|number)[]){ - this.tracks=tracks; - this.name=name; - } - static parse(read:BinRead,trackarr:Track[]):Audio{ - const name=read.readString8(); - const length=read.read16(); - const tracks:(Track|number)[]=[] - for(let i=0;isetTimeout(res,thing)); - } - } - } +export class Audio { + name: string; + tracks: (Track | number)[]; + constructor(name: string, tracks: (Track | number)[]) { + this.tracks = tracks; + this.name = name; + } + static parse(read: BinRead, trackarr: Track[]): Audio { + const name = read.readString8(); + const length = read.read16(); + const tracks: (Track | number)[] = []; + for (let i = 0; i < length; i++) { + let index = read.read16(); + if (index === 0) { + tracks.push(read.readFloat32()); + } else { + tracks.push(trackarr[index - 1]); + } + } + return new Audio(name, tracks); + } + async play() { + for (const thing of this.tracks) { + if (thing instanceof Track) { + thing.play(); + } else { + await new Promise((res) => setTimeout(res, thing)); + } + } + } } diff --git a/src/webpage/audio/index.html b/src/webpage/audio/index.html index 1d71d8f2..360beb53 100644 --- a/src/webpage/audio/index.html +++ b/src/webpage/audio/index.html @@ -1,26 +1,39 @@ - + - - - + + Jank Audio - - - - - - - + + + + + + + - +

This will eventually be something

-

I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience.

+

+ I want to let the sound system of jank not be so hard coded, but I still need to work on + everything a bit before that can happen. Thanks for your patience. +

why does this tool need to exist?

-

For size reasons jank does not use normal sound files, so I need to make this whole format to be more adaptable

+

+ For size reasons jank does not use normal sound files, so I need to make this whole format to + be more adaptable +

- diff --git a/src/webpage/audio/page.ts b/src/webpage/audio/page.ts index ccc5d45a..8905bd5f 100644 --- a/src/webpage/audio/page.ts +++ b/src/webpage/audio/page.ts @@ -1,9 +1,9 @@ -import { BinWrite } from "../utils/binaryUtils.js"; -import { setTheme } from "../utils/utils.js"; -import { Play } from "./play.js"; +import {BinWrite} from "../utils/binaryUtils.js"; +import {setTheme} from "../utils/utils.js"; +import {Play} from "./play.js"; setTheme(); -const w=new BinWrite(2**12); +const w = new BinWrite(2 ** 12); w.writeStringNo("jasf"); w.write8(4); @@ -18,100 +18,103 @@ w.writeString8("custom"); w.write32Float(150); //return Math.sin(((t + 2) ** Math.cos(t * 4)) * Math.PI * 2 * freq); //Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f))) -w.write8(4);//sin -w.write8(5)//times +w.write8(4); //sin +w.write8(5); //times { - w.write8(9);//Power - - { - w.write8(6);//adding - w.write8(1);//t - w.write8(0);w.write32Float(2);//2 - } - w.write8(13);//cos - w.write8(5);// times - w.write8(1);//t - w.write8(0);w.write32Float(4);//4 + w.write8(9); //Power + + { + w.write8(6); //adding + w.write8(1); //t + w.write8(0); + w.write32Float(2); //2 + } + w.write8(13); //cos + w.write8(5); // times + w.write8(1); //t + w.write8(0); + w.write32Float(4); //4 } { - w.write8(5)//times - w.write8(5)//times - w.write8(3);//PI - w.write8(0);w.write32Float(2);//2 - w.write8(2);//freq + w.write8(5); //times + w.write8(5); //times + w.write8(3); //PI + w.write8(0); + w.write32Float(2); //2 + w.write8(2); //freq } -w.write16(4);//3 tracks +w.write16(4); //3 tracks -w.write16(1);//zip +w.write16(1); //zip w.write8(4); -w.write32Float(1) -w.write32Float(700) +w.write32Float(1); +w.write32Float(700); -w.write16(3);//beep +w.write16(3); //beep { - w.write8(1); - w.write32Float(1) - w.write32Float(700); - w.write32Float(50); - - w.write8(0); - w.write32Float(100); - - w.write8(1); - w.write32Float(1) - w.write32Float(700); - w.write32Float(50); + w.write8(1); + w.write32Float(1); + w.write32Float(700); + w.write32Float(50); + + w.write8(0); + w.write32Float(100); + + w.write8(1); + w.write32Float(1); + w.write32Float(700); + w.write32Float(50); } -w.write16(5);//three +w.write16(5); //three { - w.write8(1); - w.write32Float(1) - w.write32Float(800); - w.write32Float(50); - - w.write8(0); - w.write32Float(50); - - w.write8(1); - w.write32Float(1) - w.write32Float(1000); - w.write32Float(50); - - w.write8(0); - w.write32Float(50); - - w.write8(1); - w.write32Float(1) - w.write32Float(1300); - w.write32Float(50); + w.write8(1); + w.write32Float(1); + w.write32Float(800); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(1); + w.write32Float(1); + w.write32Float(1000); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(1); + w.write32Float(1); + w.write32Float(1300); + w.write32Float(50); } -w.write16(5);//square +w.write16(5); //square { - w.write8(3); - w.write32Float(1) - w.write32Float(600); - w.write32Float(50); - - w.write8(0); - w.write32Float(50); - - w.write8(3); - w.write32Float(1) - w.write32Float(800); - w.write32Float(50); - - w.write8(0); - w.write32Float(50); - - w.write8(3); - w.write32Float(1) - w.write32Float(1000); - w.write32Float(50); + w.write8(3); + w.write32Float(1); + w.write32Float(600); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(3); + w.write32Float(1); + w.write32Float(800); + w.write32Float(50); + + w.write8(0); + w.write32Float(50); + + w.write8(3); + w.write32Float(1); + w.write32Float(1000); + w.write32Float(50); } -w.write16(4);//2 audio +w.write16(4); //2 audio w.writeString8("zip"); w.write16(1); @@ -128,8 +131,8 @@ w.write16(3); w.writeString8("square"); w.write16(1); w.write16(4); -const buff=w.getBuffer(); -const play=Play.parseBin(buff); +const buff = w.getBuffer(); +const play = Play.parseBin(buff); /* const zip=play.audios.get("square"); if(zip){ @@ -140,18 +143,18 @@ if(zip){ console.log(play.voices[3][0].info.wave) }; */ -console.log(play,buff); - -const download=document.getElementById("download"); -if(download){ - download.onclick=()=>{ - const blob = new Blob([buff], { type: "binary" }); - const downloadUrl = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = downloadUrl; - a.download = "sounds.jasf"; - document.body.appendChild(a); - a.click(); - URL.revokeObjectURL(downloadUrl); - } +console.log(play, buff); + +const download = document.getElementById("download"); +if (download) { + download.onclick = () => { + const blob = new Blob([buff], {type: "binary"}); + const downloadUrl = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = downloadUrl; + a.download = "sounds.jasf"; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(downloadUrl); + }; } diff --git a/src/webpage/audio/play.ts b/src/webpage/audio/play.ts index 8ae81f96..eb6adca3 100644 --- a/src/webpage/audio/play.ts +++ b/src/webpage/audio/play.ts @@ -1,48 +1,48 @@ -import { BinRead } from "../utils/binaryUtils.js"; -import { Track } from "./track.js"; -import { AVoice } from "./voice.js"; -import { Audio } from "./audio.js"; -export class Play{ - voices:[AVoice,string][] - tracks:Track[] - audios:Map; - constructor(voices:[AVoice,string][],tracks:Track[],audios:Map){ - this.voices=voices; - this.tracks=tracks; - this.audios=audios; - } - static parseBin(buffer:ArrayBuffer){ - const read=new BinRead(buffer); - if(read.readStringNo(4)!=="jasf") throw new Error("this is not a jasf file"); - let voices=read.read8(); - let six=false; - if(voices===255){ - voices=read.read16(); - six=true; - } - const voiceArr:[AVoice,string][]=[]; - for(let i=0;i; + constructor(voices: [AVoice, string][], tracks: Track[], audios: Map) { + this.voices = voices; + this.tracks = tracks; + this.audios = audios; + } + static parseBin(buffer: ArrayBuffer) { + const read = new BinRead(buffer); + if (read.readStringNo(4) !== "jasf") throw new Error("this is not a jasf file"); + let voices = read.read8(); + let six = false; + if (voices === 255) { + voices = read.read16(); + six = true; + } + const voiceArr: [AVoice, string][] = []; + for (let i = 0; i < voices; i++) { + voiceArr.push(AVoice.getVoice(read)); + } - const tracks=read.read16(); - const trackArr:Track[]=[]; - for(let i=0;i(); - for(let i=0;i(); + for (let i = 0; i < audios; i++) { + const a = Audio.parse(read, trackArr); + audioArr.set(a.name, a); + } - return new Play(voiceArr,trackArr,audioArr); - } - static async playURL(url:string){ - const res=await fetch(url); - const arr=await res.arrayBuffer(); - return this.parseBin(arr); - } + return new Play(voiceArr, trackArr, audioArr); + } + static async playURL(url: string) { + const res = await fetch(url); + const arr = await res.arrayBuffer(); + return this.parseBin(arr); + } } diff --git a/src/webpage/audio/track.ts b/src/webpage/audio/track.ts index dac4af31..0e201448 100644 --- a/src/webpage/audio/track.ts +++ b/src/webpage/audio/track.ts @@ -1,46 +1,45 @@ -import { BinRead } from "../utils/binaryUtils.js"; -import { AVoice } from "./voice.js"; +import {BinRead} from "../utils/binaryUtils.js"; +import {AVoice} from "./voice.js"; -export class Track{ - seq:(AVoice|number)[]; - constructor(playing:(AVoice|number)[]){ - this.seq=playing; - } - static parse(read:BinRead,play:[AVoice,string][],six:boolean):Track{ - const length=read.read16(); - const play2:(AVoice|number)[]=[]; - for(let i=0;isetTimeout(res,thing)); - } - } - } +export class Track { + seq: (AVoice | number)[]; + constructor(playing: (AVoice | number)[]) { + this.seq = playing; + } + static parse(read: BinRead, play: [AVoice, string][], six: boolean): Track { + const length = read.read16(); + const play2: (AVoice | number)[] = []; + for (let i = 0; i < length; i++) { + let index: number; + if (six) { + index = read.read16(); + } else { + index = read.read8(); + } + if (index === 0) { + play2.push(read.readFloat32()); + continue; + } + index--; + if (!play[index]) throw new Error("voice not found"); + const [voice] = play[index]; + let temp: AVoice; + if (voice.info.wave instanceof Function) { + temp = voice.clone(read.readFloat32(), read.readFloat32()); + } else { + temp = voice.clone(read.readFloat32(), read.readFloat32(), read.readFloat32()); + } + play2.push(temp); + } + return new Track(play2); + } + async play() { + for (const thing of this.seq) { + if (thing instanceof AVoice) { + thing.playL(); + } else { + await new Promise((res) => setTimeout(res, thing)); + } + } + } } diff --git a/src/webpage/audio/voice.ts b/src/webpage/audio/voice.ts index 703fa126..69f1287d 100644 --- a/src/webpage/audio/voice.ts +++ b/src/webpage/audio/voice.ts @@ -1,23 +1,23 @@ -import { BinRead } from "../utils/binaryUtils.js"; +import {BinRead} from "../utils/binaryUtils.js"; -class AVoice{ +class AVoice { audioCtx: AudioContext; - info: { wave: string | Function; freq: number }; + info: {wave: string | Function; freq: number}; playing: boolean; myArrayBuffer: AudioBuffer; gainNode: GainNode; buffer: Float32Array; source: AudioBufferSourceNode; - length=1; - constructor(wave: string | Function, freq: number, volume = 1,length=1000){ - this.length=length; + length = 1; + constructor(wave: string | Function, freq: number, volume = 1, length = 1000) { + this.length = length; this.audioCtx = new window.AudioContext(); - this.info = { wave, freq }; + this.info = {wave, freq}; this.playing = false; this.myArrayBuffer = this.audioCtx.createBuffer( 1, - this.audioCtx.sampleRate*length/1000, - this.audioCtx.sampleRate + (this.audioCtx.sampleRate * length) / 1000, + this.audioCtx.sampleRate, ); this.gainNode = this.audioCtx.createGain(); this.gainNode.gain.value = volume; @@ -29,193 +29,193 @@ class AVoice{ this.source.start(); this.updateWave(); } - clone(volume:number,freq:number,length=this.length){ - return new AVoice(this.wave,freq,volume,length); + clone(volume: number, freq: number, length = this.length) { + return new AVoice(this.wave, freq, volume, length); } - get wave(): string | Function{ + get wave(): string | Function { return this.info.wave; } - get freq(): number{ + get freq(): number { return this.info.freq; } - set wave(wave: string | Function){ + set wave(wave: string | Function) { this.info.wave = wave; this.updateWave(); } - set freq(freq: number){ + set freq(freq: number) { this.info.freq = freq; this.updateWave(); } - updateWave(): void{ + updateWave(): void { const func = this.waveFunction(); - for(let i = 0; i < this.buffer.length; i++){ + for (let i = 0; i < this.buffer.length; i++) { this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); } } - waveFunction(): Function{ - if(typeof this.wave === "function"){ + waveFunction(): Function { + if (typeof this.wave === "function") { return this.wave; } - switch(this.wave){ - case"sin": - return(t: number, freq: number)=>{ - return Math.sin(t * Math.PI * 2 * freq); - }; - case"triangle": - return(t: number, freq: number)=>{ - return Math.abs(((4 * t * freq) % 4) - 2) - 1; - }; - case"sawtooth": - return(t: number, freq: number)=>{ - return((t * freq) % 1) * 2 - 1; - }; - case"square": - return(t: number, freq: number)=>{ - return(t * freq) % 2 < 1 ? 1 : -1; - }; - case"white": - return(_t: number, _freq: number)=>{ - return Math.random() * 2 - 1; - }; + switch (this.wave) { + case "sin": + return (t: number, freq: number) => { + return Math.sin(t * Math.PI * 2 * freq); + }; + case "triangle": + return (t: number, freq: number) => { + return Math.abs(((4 * t * freq) % 4) - 2) - 1; + }; + case "sawtooth": + return (t: number, freq: number) => { + return ((t * freq) % 1) * 2 - 1; + }; + case "square": + return (t: number, freq: number) => { + return (t * freq) % 2 < 1 ? 1 : -1; + }; + case "white": + return (_t: number, _freq: number) => { + return Math.random() * 2 - 1; + }; } return new Function(); } - play(): void{ - if(this.playing){ + play(): void { + if (this.playing) { return; } this.source.connect(this.gainNode); this.playing = true; } - playL(){ + playL() { this.play(); - setTimeout(()=>{ + setTimeout(() => { this.stop(); - },this.length); + }, this.length); } - stop(): void{ - if(this.playing){ + stop(): void { + if (this.playing) { this.source.disconnect(); this.playing = false; } } - static noises(noise: string): void{ - switch(noise){ - case"three": { - const voicy = new AVoice("sin", 800); - voicy.play(); - setTimeout(_=>{ - voicy.freq = 1000; - }, 50); - setTimeout(_=>{ - voicy.freq = 1300; - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"zip": { - const voicy = new AVoice((t: number, freq: number)=>{ - return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); - }, 700); - voicy.play(); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"square": { - const voicy = new AVoice("square", 600, 0.4); - voicy.play(); - setTimeout(_=>{ - voicy.freq = 800; - }, 50); - setTimeout(_=>{ - voicy.freq = 1000; - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case"beep": { - const voicy = new AVoice("sin", 800); - voicy.play(); - setTimeout(_=>{ - voicy.stop(); - }, 50); - setTimeout(_=>{ + static noises(noise: string): void { + switch (noise) { + case "three": { + const voicy = new AVoice("sin", 800); voicy.play(); - }, 100); - setTimeout(_=>{ - voicy.stop(); - }, 150); - break; - } - case "join":{ - const voicy = new AVoice("triangle", 600,.1); - voicy.play(); - setTimeout(_=>{ - voicy.freq=800; - }, 75); - setTimeout(_=>{ - voicy.freq=1000; - }, 150); - setTimeout(_=>{ - voicy.stop(); - }, 200); - break; - } - case "leave":{ - const voicy = new AVoice("triangle", 850,.5); - voicy.play(); - setTimeout(_=>{ - voicy.freq=700; - }, 100); - setTimeout(_=>{ - voicy.stop(); - voicy.freq=400; - }, 180); - setTimeout(_=>{ + setTimeout((_) => { + voicy.freq = 1000; + }, 50); + setTimeout((_) => { + voicy.freq = 1300; + }, 100); + setTimeout((_) => { + voicy.stop(); + }, 150); + break; + } + case "zip": { + const voicy = new AVoice((t: number, freq: number) => { + return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); + }, 700); voicy.play(); - }, 200); - setTimeout(_=>{ - voicy.stop(); - }, 250); - break; - } + setTimeout((_) => { + voicy.stop(); + }, 150); + break; + } + case "square": { + const voicy = new AVoice("square", 600, 0.4); + voicy.play(); + setTimeout((_) => { + voicy.freq = 800; + }, 50); + setTimeout((_) => { + voicy.freq = 1000; + }, 100); + setTimeout((_) => { + voicy.stop(); + }, 150); + break; + } + case "beep": { + const voicy = new AVoice("sin", 800); + voicy.play(); + setTimeout((_) => { + voicy.stop(); + }, 50); + setTimeout((_) => { + voicy.play(); + }, 100); + setTimeout((_) => { + voicy.stop(); + }, 150); + break; + } + case "join": { + const voicy = new AVoice("triangle", 600, 0.1); + voicy.play(); + setTimeout((_) => { + voicy.freq = 800; + }, 75); + setTimeout((_) => { + voicy.freq = 1000; + }, 150); + setTimeout((_) => { + voicy.stop(); + }, 200); + break; + } + case "leave": { + const voicy = new AVoice("triangle", 850, 0.5); + voicy.play(); + setTimeout((_) => { + voicy.freq = 700; + }, 100); + setTimeout((_) => { + voicy.stop(); + voicy.freq = 400; + }, 180); + setTimeout((_) => { + voicy.play(); + }, 200); + setTimeout((_) => { + voicy.stop(); + }, 250); + break; + } } } - static get sounds(){ - return["three", "zip", "square", "beep"]; + static get sounds() { + return ["three", "zip", "square", "beep"]; } - static getVoice(read:BinRead):[AVoice,string]{ + static getVoice(read: BinRead): [AVoice, string] { const name = read.readString8(); - let length=read.readFloat32(); - let special:Function|string - if(length!==0){ - special=this.parseExpression(read); - }else{ - special=name; - length=1; + let length = read.readFloat32(); + let special: Function | string; + if (length !== 0) { + special = this.parseExpression(read); + } else { + special = name; + length = 1; } - return [new AVoice(special,0,0,length),name] + return [new AVoice(special, 0, 0, length), name]; } - static parseExpression(read:BinRead):Function{ - return new Function("t","f",`return ${this.PEHelper(read)};`); + static parseExpression(read: BinRead): Function { + return new Function("t", "f", `return ${this.PEHelper(read)};`); } - static PEHelper(read:BinRead):string{ - let state=read.read8(); - switch(state){ + static PEHelper(read: BinRead): string { + let state = read.read8(); + switch (state) { case 0: - return ""+read.readFloat32(); + return "" + read.readFloat32(); case 1: return "t"; case 2: return "f"; case 3: - return `Math.PI` + return `Math.PI`; case 4: return `Math.sin(${this.PEHelper(read)})`; case 5: @@ -238,9 +238,8 @@ class AVoice{ return `Math.cos(${this.PEHelper(read)})`; default: throw new Error("unexpected case found!"); - } } } -export{ AVoice as AVoice }; +export {AVoice as AVoice}; diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 9b0d4295..cd043d46 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1,27 +1,34 @@ "use strict"; -import{ Message }from"./message.js"; -import{ AVoice }from"./audio/voice.js"; -import{ Contextmenu }from"./contextmenu.js"; -import{ Guild }from"./guild.js"; -import{ Localuser }from"./localuser.js"; -import{ Permissions }from"./permissions.js"; -import{ Dialog, Float, Settings }from"./settings.js"; -import{ Role, RoleList }from"./role.js"; -import{ InfiniteScroller }from"./infiniteScroller.js"; -import{ SnowFlake }from"./snowflake.js"; -import{channeljson,embedjson,messageCreateJson,messagejson,readyjson,startTypingjson}from"./jsontypes.js"; -import{ MarkDown }from"./markdown.js"; -import{ Member }from"./member.js"; -import { Voice } from "./voice.js"; -import { User } from "./user.js"; -import { I18n } from "./i18n.js"; +import {Message} from "./message.js"; +import {AVoice} from "./audio/voice.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Guild} from "./guild.js"; +import {Localuser} from "./localuser.js"; +import {Permissions} from "./permissions.js"; +import {Dialog, Float, Settings} from "./settings.js"; +import {Role, RoleList} from "./role.js"; +import {InfiniteScroller} from "./infiniteScroller.js"; +import {SnowFlake} from "./snowflake.js"; +import { + channeljson, + embedjson, + messageCreateJson, + messagejson, + readyjson, + startTypingjson, +} from "./jsontypes.js"; +import {MarkDown} from "./markdown.js"; +import {Member} from "./member.js"; +import {Voice} from "./voice.js"; +import {User} from "./user.js"; +import {I18n} from "./i18n.js"; declare global { -interface NotificationOptions { -image?: string | null | undefined; -} + interface NotificationOptions { + image?: string | null | undefined; + } } -class Channel extends SnowFlake{ +class Channel extends SnowFlake { editing!: Message | null; type!: number; owner!: Guild; @@ -38,11 +45,11 @@ class Channel extends SnowFlake{ position: number = 0; lastreadmessageid: string | undefined; lastmessageid: string | undefined; - mentions=0; + mentions = 0; lastpin!: string; move_id?: string; typing!: number; - message_notifications:number=3; + message_notifications: number = 3; allthewayup!: boolean; static contextmenu = new Contextmenu("channel menu"); replyingto!: Message | null; @@ -50,60 +57,76 @@ class Channel extends SnowFlake{ idToPrev: Map = new Map(); idToNext: Map = new Map(); messages: Map = new Map(); - voice?:Voice; - bitrate:number=128000; + voice?: Voice; + bitrate: number = 128000; - muted:boolean=false; - mute_config= {selected_time_window: -1,end_time: 0} - handleUserOverrides(settings:{message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}){ - this.message_notifications=settings.message_notifications; - this.muted=settings.muted; - this.mute_config=settings.mute_config; + muted: boolean = false; + mute_config = {selected_time_window: -1, end_time: 0}; + handleUserOverrides(settings: { + message_notifications: number; + muted: boolean; + mute_config: {selected_time_window: number; end_time: number}; + channel_id: string; + }) { + this.message_notifications = settings.message_notifications; + this.muted = settings.muted; + this.mute_config = settings.mute_config; } - static setupcontextmenu(){ - this.contextmenu.addbutton(()=>I18n.getTranslation("channel.copyId"), function(this: Channel){ - navigator.clipboard.writeText(this.id); - }); + static setupcontextmenu() { + this.contextmenu.addbutton( + () => I18n.getTranslation("channel.copyId"), + function (this: Channel) { + navigator.clipboard.writeText(this.id); + }, + ); - this.contextmenu.addbutton(()=>I18n.getTranslation("channel.markRead"), function(this: Channel){ - this.readbottom(); - }); + this.contextmenu.addbutton( + () => I18n.getTranslation("channel.markRead"), + function (this: Channel) { + this.readbottom(); + }, + ); - this.contextmenu.addbutton(()=>I18n.getTranslation("channel.settings"), function(this: Channel){ - this.generateSettings(); - },null,function(){ - return this.hasPermission("MANAGE_CHANNELS"); - }); + this.contextmenu.addbutton( + () => I18n.getTranslation("channel.settings"), + function (this: Channel) { + this.generateSettings(); + }, + null, + function () { + return this.hasPermission("MANAGE_CHANNELS"); + }, + ); this.contextmenu.addbutton( - ()=>I18n.getTranslation("channel.delete"), - function(this: Channel){ + () => I18n.getTranslation("channel.delete"), + function (this: Channel) { this.deleteChannel(); }, null, - function(){ + function () { return this.isAdmin(); - } + }, ); this.contextmenu.addbutton( - ()=>I18n.getTranslation("guild.notifications"), - function(){ + () => I18n.getTranslation("guild.notifications"), + function () { this.setnotifcation(); - } - ) + }, + ); this.contextmenu.addbutton( - ()=>I18n.getTranslation("channel.makeInvite"), - function(this: Channel){ + () => I18n.getTranslation("channel.makeInvite"), + function (this: Channel) { this.createInvite(); }, null, - function(){ + function () { return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; - } + }, ); } - createInvite(){ + createInvite() { const div = document.createElement("div"); div.classList.add("invitediv"); const text = document.createElement("span"); @@ -116,13 +139,13 @@ class Channel extends SnowFlake{ const copy = document.createElement("span"); copy.classList.add("copybutton", "svgicon", "svg-copy"); copycontainer.append(copy); - copycontainer.onclick = _=>{ - if(text.textContent){ + copycontainer.onclick = (_) => { + if (text.textContent) { navigator.clipboard.writeText(text.textContent); } }; div.append(copycontainer); - const update = ()=>{ + const update = () => { fetch(`${this.info.api}/channels/${this.id}/invites`, { method: "POST", headers: this.headers, @@ -135,8 +158,8 @@ class Channel extends SnowFlake{ temporary: uses !== 0, }), }) - .then(_=>_.json()) - .then(json=>{ + .then((_) => _.json()) + .then((json) => { const params = new URLSearchParams(""); params.set("instance", this.info.wellknown); const encoded = params.toString(); @@ -144,45 +167,75 @@ class Channel extends SnowFlake{ }); }; update(); - const inviteOptions=new Dialog("",{noSubmit:true}); + const inviteOptions = new Dialog("", {noSubmit: true}); inviteOptions.options.addTitle(I18n.getTranslation("inviteOptions.title")); - inviteOptions.options.addText(I18n.getTranslation("invite.subtext",this.name,this.guild.properties.name)); + inviteOptions.options.addText( + I18n.getTranslation("invite.subtext", this.name, this.guild.properties.name), + ); - inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, - ["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) - ).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];update()}; + inviteOptions.options.addSelect( + I18n.getTranslation("invite.expireAfter"), + () => {}, + ["30m", "1h", "6h", "12h", "1d", "7d", "30d", "never"].map((e) => + I18n.getTranslation("inviteOptions." + e), + ), + ).onchange = (e) => { + expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e]; + update(); + }; - const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) - timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) - inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) - .onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];update()}; + const timeOptions = ["1", "5", "10", "25", "50", "100"].map((e) => + I18n.getTranslation("inviteOptions.limit", e), + ); + timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")); + inviteOptions.options.addSelect( + I18n.getTranslation("invite.expireAfter"), + () => {}, + timeOptions, + ).onchange = (e) => { + uses = [0, 1, 5, 10, 25, 50, 100][e]; + update(); + }; inviteOptions.options.addHTMLArea(div); inviteOptions.show(); } - generateSettings(){ + generateSettings() { this.sortPerms(); - const settings = new Settings(I18n.getTranslation("channel.settingsFor",this.name)); + const settings = new Settings(I18n.getTranslation("channel.settingsFor", this.name)); { - const gensettings=settings.addButton("Settings"); - const form=gensettings.addForm("",()=>{},{ - fetchURL:this.info.api + "/channels/" + this.id, + const gensettings = settings.addButton("Settings"); + const form = gensettings.addForm("", () => {}, { + fetchURL: this.info.api + "/channels/" + this.id, method: "PATCH", headers: this.headers, }); - form.addTextInput(I18n.getTranslation("channel.name:"),"name",{initText:this.name}); - form.addMDInput(I18n.getTranslation("channel.topic:"),"topic",{initText:this.topic}); - form.addCheckboxInput(I18n.getTranslation("channel.nsfw:"),"nsfw",{initState:this.nsfw}); - if(this.type!==4){ - const options=["voice", "text", "announcement"]; - form.addSelect("Type:","type",options.map(e=>I18n.getTranslation("channel."+e)),{ - defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string) - },options); - form.addPreprocessor((obj:any)=>{ - obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string] - }) + form.addTextInput(I18n.getTranslation("channel.name:"), "name", { + initText: this.name, + }); + form.addMDInput(I18n.getTranslation("channel.topic:"), "topic", { + initText: this.topic, + }); + form.addCheckboxInput(I18n.getTranslation("channel.nsfw:"), "nsfw", { + initState: this.nsfw, + }); + if (this.type !== 4) { + const options = ["voice", "text", "announcement"]; + form.addSelect( + "Type:", + "type", + options.map((e) => I18n.getTranslation("channel." + e)), + { + defaultIndex: options.indexOf( + {0: "text", 2: "voice", 5: "announcement", 4: "category"}[this.type] as string, + ), + }, + options, + ); + form.addPreprocessor((obj: any) => { + obj.type = {text: 0, voice: 2, announcement: 5, category: 4}[obj.type as string]; + }); } - } const s1 = settings.addButton("Permissions"); s1.options.push( @@ -190,78 +243,70 @@ class Channel extends SnowFlake{ this.permission_overwritesar, this.guild, this.updateRolePermissions.bind(this), - this - ) + this, + ), ); settings.show(); } - sortPerms(){ - this.permission_overwritesar.sort((a, b)=>{ - return( - this.guild.roles.indexOf(a[0]) - - this.guild.roles.indexOf(b[0]) - ); + sortPerms() { + this.permission_overwritesar.sort((a, b) => { + return this.guild.roles.indexOf(a[0]) - this.guild.roles.indexOf(b[0]); }); } - setUpInfiniteScroller(){ + setUpInfiniteScroller() { this.infinite = new InfiniteScroller( - async (id: string, offset: number): Promise=>{ - if(offset === 1){ - if(this.idToPrev.has(id)){ + async (id: string, offset: number): Promise => { + if (offset === 1) { + if (this.idToPrev.has(id)) { return this.idToPrev.get(id); - }else{ + } else { await this.grabBefore(id); return this.idToPrev.get(id); } - }else{ - if(this.idToNext.has(id)){ + } else { + if (this.idToNext.has(id)) { return this.idToNext.get(id); - }else if(this.lastmessage?.id !== id){ + } else if (this.lastmessage?.id !== id) { await this.grabAfter(id); return this.idToNext.get(id); - }else{ - + } else { } } return undefined; }, - async (id: string): Promise=>{ + async (id: string): Promise => { //await new Promise(_=>{setTimeout(_,Math.random()*10)}) const messgage = this.messages.get(id); - try{ - if(messgage){ + try { + if (messgage) { return messgage.buildhtml(); - }else{ + } else { console.error(id + " not found"); } - }catch(e){ + } catch (e) { console.error(e); } return document.createElement("div"); }, - async (id: string)=>{ + async (id: string) => { const message = this.messages.get(id); - try{ - if(message){ + try { + if (message) { message.deleteDiv(); return true; } - }catch(e){ + } catch (e) { console.error(e); - }finally{ + } finally { } return false; }, - this.readbottom.bind(this) + this.readbottom.bind(this), ); } - constructor( - json: channeljson | -1, - owner: Guild, - id: string = json === -1 ? "" : json.id - ){ + constructor(json: channeljson | -1, owner: Guild, id: string = json === -1 ? "" : json.id) { super(id); - if(json === -1){ + if (json === -1) { return; } this.editing; @@ -269,7 +314,7 @@ class Channel extends SnowFlake{ this.owner = owner; this.headers = this.owner.headers; this.name = json.name; - if(json.parent_id){ + if (json.parent_id) { this.parent_id = json.parent_id; } this.parent = undefined; @@ -277,20 +322,17 @@ class Channel extends SnowFlake{ this.guild_id = json.guild_id; this.permission_overwrites = new Map(); this.permission_overwritesar = []; - for(const thing of json.permission_overwrites){ - if(thing.id === "1182819038095799904" ||thing.id === "1182820803700625444"){ + for (const thing of json.permission_overwrites) { + if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { continue; } - if(!this.permission_overwrites.has(thing.id)){ + if (!this.permission_overwrites.has(thing.id)) { //either a bug in the server requires this, or the API is cursed - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); + this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny)); const permission = this.permission_overwrites.get(thing.id); - if(permission){ + if (permission) { const role = this.guild.roleids.get(thing.id); - if(role){ + if (role) { this.permission_overwritesar.push([role, permission]); } } @@ -301,126 +343,128 @@ class Channel extends SnowFlake{ this.nsfw = json.nsfw; this.position = json.position; this.lastreadmessageid = undefined; - if(json.last_message_id){ + if (json.last_message_id) { this.lastmessageid = json.last_message_id; - }else{ + } else { this.lastmessageid = undefined; } this.setUpInfiniteScroller(); this.perminfo ??= {}; - if(this.type===2&&this.localuser.voiceFactory){ - this.voice=this.localuser.voiceFactory.makeVoice(this.guild.id,this.id,{bitrate:this.bitrate}); + if (this.type === 2 && this.localuser.voiceFactory) { + this.voice = this.localuser.voiceFactory.makeVoice(this.guild.id, this.id, { + bitrate: this.bitrate, + }); this.setUpVoice(); } } - get perminfo(){ + get perminfo() { return this.guild.perminfo.channels[this.id]; } - set perminfo(e){ + set perminfo(e) { this.guild.perminfo.channels[this.id] = e; } - isAdmin(){ + isAdmin() { return this.guild.isAdmin(); } - get guild(){ + get guild() { return this.owner; } - get localuser(){ + get localuser() { return this.guild.localuser; } - get info(){ + get info() { return this.owner.info; } - readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]){ + readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]) { this.lastreadmessageid = json.last_message_id; this.mentions = json.mention_count; this.mentions ??= 0; this.lastpin = json.last_pin_timestamp; } - get hasunreads(): boolean{ - if(!this.hasPermission("VIEW_CHANNEL")){ + get hasunreads(): boolean { + if (!this.hasPermission("VIEW_CHANNEL")) { return false; } - return( + return ( Boolean(this.lastmessageid) && (!this.lastreadmessageid || - SnowFlake.stringToUnixTime(this.lastmessageid as string) > - SnowFlake.stringToUnixTime(this.lastreadmessageid)) && + SnowFlake.stringToUnixTime(this.lastmessageid as string) > + SnowFlake.stringToUnixTime(this.lastreadmessageid)) && this.type !== 4 ); } - hasPermission(name: string, member = this.guild.member): boolean{ - if(member.isAdmin()){ + hasPermission(name: string, member = this.guild.member): boolean { + if (member.isAdmin()) { return true; } - const roles=new Set(member.roles); - const everyone=this.guild.roles[this.guild.roles.length-1]; - if(!member.user.bot||true){ - roles.add(everyone) + const roles = new Set(member.roles); + const everyone = this.guild.roles[this.guild.roles.length - 1]; + if (!member.user.bot || true) { + roles.add(everyone); } - for(const thing of roles){ + for (const thing of roles) { const premission = this.permission_overwrites.get(thing.id); - if(premission){ + if (premission) { const perm = premission.getPermission(name); - if(perm){ + if (perm) { return perm === 1; } } - if(thing.permissions.getPermission(name)){ + if (thing.permissions.getPermission(name)) { return true; } } return false; } - get canMessage(): boolean{ - if(this.permission_overwritesar.length === 0 &&this.hasPermission("MANAGE_CHANNELS")){ - const role = this.guild.roles.find(_=>_.name === "@everyone"); - if(role){ + get canMessage(): boolean { + if (this.permission_overwritesar.length === 0 && this.hasPermission("MANAGE_CHANNELS")) { + const role = this.guild.roles.find((_) => _.name === "@everyone"); + if (role) { this.addRoleToPerms(role); } } return this.hasPermission("SEND_MESSAGES"); } - sortchildren(){ - this.children.sort((a, b)=>{ + sortchildren() { + this.children.sort((a, b) => { return a.position - b.position; }); } - resolveparent(_guild: Guild){ + resolveparent(_guild: Guild) { const parentid = this.parent_id; - if(!parentid)return false; + if (!parentid) return false; this.parent = this.localuser.channelids.get(parentid); this.parent ??= undefined; - if(this.parent !== undefined){ + if (this.parent !== undefined) { this.parent.children.push(this); } return this.parent !== undefined; } - calculateReorder(){ + calculateReorder() { let position = -1; const build: { id: string; position: number | undefined; parent_id: string | undefined; }[] = []; - for(const thing of this.children){ + for (const thing of this.children) { const thisthing: { id: string; position: number | undefined; parent_id: string | undefined; - } = { id: thing.id, position: undefined, parent_id: undefined }; + } = {id: thing.id, position: undefined, parent_id: undefined}; - if(thing.position < position){ + if (thing.position < position) { thing.position = thisthing.position = position + 1; } position = thing.position; - if(thing.move_id && thing.move_id !== thing.parent_id){ + if (thing.move_id && thing.move_id !== thing.parent_id) { thing.parent_id = thing.move_id; thisthing.parent_id = thing.parent?.id; thing.move_id = undefined; //console.log(this.guild.channelids[thisthing.parent_id.id]); } - if(thisthing.position || thisthing.parent_id){ + if (thisthing.position || thisthing.parent_id) { build.push(thisthing); } } @@ -428,35 +472,35 @@ class Channel extends SnowFlake{ } static dragged: [Channel, HTMLDivElement] | [] = []; html: WeakRef | undefined; - get visable(){ + get visable() { return this.hasPermission("VIEW_CHANNEL"); } - voiceUsers=new WeakRef(document.createElement("div")); - createguildHTML(admin = false): HTMLDivElement{ + voiceUsers = new WeakRef(document.createElement("div")); + createguildHTML(admin = false): HTMLDivElement { const div = document.createElement("div"); this.html = new WeakRef(div); - if(!this.visable){ + if (!this.visable) { let quit = true; - for(const thing of this.children){ - if(thing.visable){ + for (const thing of this.children) { + if (thing.visable) { quit = false; } } - if(quit){ + if (quit) { return div; } } // @ts-ignore I dont wanna deal with this div.all = this; div.draggable = admin; - div.addEventListener("dragstart", e=>{ + div.addEventListener("dragstart", (e) => { Channel.dragged = [this, div]; e.stopImmediatePropagation(); }); - div.addEventListener("dragend", ()=>{ + div.addEventListener("dragend", () => { Channel.dragged = []; }); - if(this.type === 4){ + if (this.type === 4) { this.sortchildren(); const caps = document.createElement("div"); @@ -471,56 +515,56 @@ class Channel extends SnowFlake{ decdiv.appendChild(myhtml); caps.appendChild(decdiv); const childrendiv = document.createElement("div"); - if(admin){ + if (admin) { const addchannel = document.createElement("span"); - addchannel.classList.add("addchannel","svgicon","svg-plus"); + addchannel.classList.add("addchannel", "svgicon", "svg-plus"); caps.appendChild(addchannel); - addchannel.onclick = _=>{ + addchannel.onclick = (_) => { this.guild.createchannels(this.createChannel.bind(this)); }; this.coatDropDiv(decdiv, childrendiv); } div.appendChild(caps); - caps.classList.add("flexltr","capsflex"); - decdiv.classList.add("flexltr","channeleffects"); + caps.classList.add("flexltr", "capsflex"); + decdiv.classList.add("flexltr", "channeleffects"); decdiv.classList.add("channel"); - Channel.contextmenu.bindContextmenu(decdiv, this,undefined); + Channel.contextmenu.bindContextmenu(decdiv, this, undefined); // @ts-ignore I dont wanna deal with this decdiv.all = this; - for(const channel of this.children){ + for (const channel of this.children) { childrendiv.appendChild(channel.createguildHTML(admin)); } childrendiv.classList.add("channels"); - setTimeout((_: any)=>{ - if(!this.perminfo.collapsed){ + setTimeout((_: any) => { + if (!this.perminfo.collapsed) { childrendiv.style.height = childrendiv.scrollHeight + "px"; } }, 100); div.appendChild(childrendiv); - if(this.perminfo.collapsed){ + if (this.perminfo.collapsed) { decoration.classList.add("hiddencat"); childrendiv.style.height = "0px"; } - decdiv.onclick = ()=>{ - if(childrendiv.style.height !== "0px"){ + decdiv.onclick = () => { + if (childrendiv.style.height !== "0px") { decoration.classList.add("hiddencat"); this.perminfo.collapsed = true; this.localuser.userinfo.updateLocal(); childrendiv.style.height = "0px"; - }else{ + } else { decoration.classList.remove("hiddencat"); this.perminfo.collapsed = false; this.localuser.userinfo.updateLocal(); childrendiv.style.height = childrendiv.scrollHeight + "px"; } }; - }else{ + } else { div.classList.add("channel"); this.unreads(); - Channel.contextmenu.bindContextmenu(div, this,undefined); - if(admin){ + Channel.contextmenu.bindContextmenu(div, this, undefined); + if (admin) { this.coatDropDiv(div); } // @ts-ignore I dont wanna deal with this @@ -531,171 +575,174 @@ class Channel extends SnowFlake{ const myhtml = document.createElement("span"); myhtml.classList.add("ellipsis"); myhtml.textContent = this.name; - if(this.type === 0){ + if (this.type === 0) { const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", this.nsfw?"svg-channelnsfw":"svg-channel"); - }else if(this.type === 2){ + decoration.classList.add("space", "svgicon", this.nsfw ? "svg-channelnsfw" : "svg-channel"); + } else if (this.type === 2) { // const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", this.nsfw?"svg-voicensfw":"svg-voice"); - }else if(this.type === 5){ + decoration.classList.add("space", "svgicon", this.nsfw ? "svg-voicensfw" : "svg-voice"); + } else if (this.type === 5) { // const decoration = document.createElement("span"); button.appendChild(decoration); - decoration.classList.add("space", "svgicon", this.nsfw?"svg-announcensfw":"svg-announce"); - }else{ + decoration.classList.add( + "space", + "svgicon", + this.nsfw ? "svg-announcensfw" : "svg-announce", + ); + } else { console.log(this.type); } button.appendChild(myhtml); - button.onclick = _=>{ + button.onclick = (_) => { this.getHTML(); const toggle = document.getElementById("maintoggle") as HTMLInputElement; toggle.checked = true; }; - if(this.type===2){ - const voiceUsers=document.createElement("div"); + if (this.type === 2) { + const voiceUsers = document.createElement("div"); div.append(voiceUsers); - this.voiceUsers=new WeakRef(voiceUsers); + this.voiceUsers = new WeakRef(voiceUsers); this.updateVoiceUsers(); } } return div; } - async moveForDrag(x:number){ - const mainarea=document.getElementById("mainarea"); - if(!mainarea) return; - if(x===-1){ + async moveForDrag(x: number) { + const mainarea = document.getElementById("mainarea"); + if (!mainarea) return; + if (x === -1) { mainarea.style.removeProperty("left"); mainarea.style.removeProperty("transition"); return; } - mainarea.style.left=x+"px"; - mainarea.style.transition="left 0s" + mainarea.style.left = x + "px"; + mainarea.style.transition = "left 0s"; } - async setUpVoice(){ - if(!this.voice) return; - this.voice.onMemberChange=async (memb,joined)=>{ - console.log(memb,joined); - if(typeof memb!=="string"){ - await Member.new(memb,this.guild); + async setUpVoice() { + if (!this.voice) return; + this.voice.onMemberChange = async (memb, joined) => { + console.log(memb, joined); + if (typeof memb !== "string") { + await Member.new(memb, this.guild); } this.updateVoiceUsers(); - if(this.voice===this.localuser.currentVoice){ + if (this.voice === this.localuser.currentVoice) { AVoice.noises("join"); } - } + }; } - async updateVoiceUsers(){ - const voiceUsers=this.voiceUsers.deref(); - if(!voiceUsers||!this.voice) return; - console.warn(this.voice.userids) + async updateVoiceUsers() { + const voiceUsers = this.voiceUsers.deref(); + if (!voiceUsers || !this.voice) return; + console.warn(this.voice.userids); - const html=(await Promise.all(this.voice.userids.entries().toArray().map(async _=>{ - const user=await User.resolve(_[0],this.localuser); - console.log(user); - const member=await Member.resolveMember(user,this.guild); - const array=[member,_[1]] as [Member, typeof _[1]]; - return array; - }))).flatMap(([member,_obj])=>{ - if(!member){ + const html = ( + await Promise.all( + this.voice.userids + .entries() + .toArray() + .map(async (_) => { + const user = await User.resolve(_[0], this.localuser); + console.log(user); + const member = await Member.resolveMember(user, this.guild); + const array = [member, _[1]] as [Member, (typeof _)[1]]; + return array; + }), + ) + ).flatMap(([member, _obj]) => { + if (!member) { console.warn("This is weird, member doesn't exist :P"); return []; } - const div=document.createElement("div"); + const div = document.createElement("div"); div.classList.add("voiceuser"); - const span=document.createElement("span"); - span.textContent=member.name; + const span = document.createElement("span"); + span.textContent = member.name; div.append(span); return div; }); - voiceUsers.innerHTML=""; + voiceUsers.innerHTML = ""; voiceUsers.append(...html); } - get myhtml(){ - if(this.html){ + get myhtml() { + if (this.html) { return this.html.deref(); - }else{ + } else { return; } } - readbottom(){ - this.mentions=0; - if(!this.hasunreads){ + readbottom() { + this.mentions = 0; + if (!this.hasunreads) { this.guild.unreads(); return; } - fetch( - this.info.api +"/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({}), - } - ); + fetch(this.info.api + "/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", { + method: "POST", + headers: this.headers, + body: JSON.stringify({}), + }); this.lastreadmessageid = this.lastmessageid; this.guild.unreads(); this.unreads(); } - coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false){ - div.addEventListener("dragenter", event=>{ + coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) { + div.addEventListener("dragenter", (event) => { console.log("enter"); event.preventDefault(); }); - div.addEventListener("dragover", event=>{ + div.addEventListener("dragover", (event) => { event.preventDefault(); }); - div.addEventListener("drop", event=>{ + div.addEventListener("drop", (event) => { const that = Channel.dragged[0]; - if(!that)return; + if (!that) return; event.preventDefault(); - if(container){ + if (container) { that.move_id = this.id; - if(that.parent){ + if (that.parent) { that.parent.children.splice(that.parent.children.indexOf(that), 1); } that.parent = this; - (container as HTMLElement).prepend( - Channel.dragged[1] as HTMLDivElement - ); + (container as HTMLElement).prepend(Channel.dragged[1] as HTMLDivElement); this.children.unshift(that); - }else{ + } else { console.log(this, Channel.dragged); that.move_id = this.parent_id; - if(that.parent){ + if (that.parent) { that.parent.children.splice(that.parent.children.indexOf(that), 1); - }else{ - this.guild.headchannels.splice( - this.guild.headchannels.indexOf(that), - 1 - ); + } else { + this.guild.headchannels.splice(this.guild.headchannels.indexOf(that), 1); } that.parent = this.parent; - if(that.parent){ + if (that.parent) { const build: Channel[] = []; - for(let i = 0; i < that.parent.children.length; i++){ + for (let i = 0; i < that.parent.children.length; i++) { build.push(that.parent.children[i]); - if(that.parent.children[i] === this){ + if (that.parent.children[i] === this) { build.push(that); } } that.parent.children = build; - }else{ + } else { const build: Channel[] = []; - for(let i = 0; i < this.guild.headchannels.length; i++){ + for (let i = 0; i < this.guild.headchannels.length; i++) { build.push(this.guild.headchannels[i]); - if(this.guild.headchannels[i] === this){ + if (this.guild.headchannels[i] === this) { build.push(that); } } this.guild.headchannels = build; } - if(Channel.dragged[1]){ + if (Channel.dragged[1]) { div.after(Channel.dragged[1]); } } @@ -704,7 +751,7 @@ class Channel extends SnowFlake{ return div; } - createChannel(name: string, type: number){ + createChannel(name: string, type: number) { fetch(this.info.api + "/guilds/" + this.guild.id + "/channels", { method: "POST", headers: this.headers, @@ -716,34 +763,34 @@ class Channel extends SnowFlake{ }), }); } - deleteChannel(){ + deleteChannel() { fetch(this.info.api + "/channels/" + this.id, { method: "DELETE", headers: this.headers, }); } - setReplying(message: Message){ - if(this.replyingto?.div){ + setReplying(message: Message) { + if (this.replyingto?.div) { this.replyingto.div.classList.remove("replying"); } this.replyingto = message; const typebox = document.getElementById("typebox") as HTMLElement; typebox.focus(); - if(!this.replyingto?.div)return; + if (!this.replyingto?.div) return; console.log(message); this.replyingto.div.classList.add("replying"); this.makereplybox(); } - makereplybox(){ + makereplybox() { const replybox = document.getElementById("replybox") as HTMLElement; const typebox = document.getElementById("typebox") as HTMLElement; - if(this.replyingto){ + if (this.replyingto) { replybox.innerHTML = ""; const span = document.createElement("span"); span.textContent = I18n.getTranslation("replyingTo", this.replyingto.author.username); const X = document.createElement("button"); - X.onclick = _=>{ - if(this.replyingto?.div){ + X.onclick = (_) => { + if (this.replyingto?.div) { this.replyingto.div.classList.remove("replying"); } replybox.classList.add("hideReplyBox"); @@ -752,125 +799,121 @@ class Channel extends SnowFlake{ typebox.classList.remove("typeboxreplying"); }; replybox.classList.remove("hideReplyBox"); - X.classList.add("cancelReply","svgicon","svg-x"); + X.classList.add("cancelReply", "svgicon", "svg-x"); replybox.append(span); replybox.append(X); typebox.classList.add("typeboxreplying"); - }else{ + } else { replybox.classList.add("hideReplyBox"); typebox.classList.remove("typeboxreplying"); } } - async getmessage(id: string): Promise{ + async getmessage(id: string): Promise { const message = this.messages.get(id); - if(message){ + if (message) { return message; - }else{ + } else { const gety = await fetch( - this.info.api + "/channels/" +this.id +"/messages?limit=1&around=" +id, - { headers: this.headers } + this.info.api + "/channels/" + this.id + "/messages?limit=1&around=" + id, + {headers: this.headers}, ); const json = await gety.json(); - if(json.length===0){ + if (json.length === 0) { return undefined; } return new Message(json[0], this); } } - async focus(id:string){ - console.time() + async focus(id: string) { + console.time(); console.log(await this.getmessage(id)); await this.getHTML(); - console.timeEnd() + console.timeEnd(); console.warn(id); this.infinite.focus(id); } - editLast(){ - let message:Message|undefined=this.lastmessage; - while(message&&message.author!==this.localuser.user){ - message=this.messages.get(this.idToPrev.get(message.id) as string); + editLast() { + let message: Message | undefined = this.lastmessage; + while (message && message.author !== this.localuser.user) { + message = this.messages.get(this.idToPrev.get(message.id) as string); } - if(message){ + if (message) { message.setEdit(); } } static genid: number = 0; - nsfwPannel(){ - (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+false; - (document.getElementById("upload") as HTMLElement).style.visibility="hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility="hidden"; + nsfwPannel() { + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + false; + (document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; const messages = document.getElementById("channelw") as HTMLDivElement; - const messageContainers = Array.from( - messages.getElementsByClassName("messagecontainer") - ); - for(const thing of messageContainers){ + const messageContainers = Array.from(messages.getElementsByClassName("messagecontainer")); + for (const thing of messageContainers) { thing.remove(); } const elements = Array.from(messages.getElementsByClassName("scroller")); - for(const elm of elements){ + for (const elm of elements) { elm.remove(); console.warn("rouge element detected and removed"); } - const div=document.getElementById("sideDiv") as HTMLDivElement; - div.innerHTML=""; - const float=new Float(""); - const options=float.options; + const div = document.getElementById("sideDiv") as HTMLDivElement; + div.innerHTML = ""; + const float = new Float(""); + const options = float.options; //@ts-ignore weird hack, ik, but the user here does have that information //TODO make an extention of the user class with these aditional properties //TODO make a popup for `nsfw_allowed==null` to input age - if(this.localuser.user.nsfw_allowed){ + if (this.localuser.user.nsfw_allowed) { options.addTitle("This is a NSFW channel, do you wish to proceed?"); - const buttons=options.addOptions("",{ltr:true}); - buttons.addButtonInput("","Yes",()=>{ - this.perminfo.nsfwOk=true; + const buttons = options.addOptions("", {ltr: true}); + buttons.addButtonInput("", "Yes", () => { + this.perminfo.nsfwOk = true; this.localuser.userinfo.updateLocal(); this.getHTML(); }); - buttons.addButtonInput("","No",()=>{ + buttons.addButtonInput("", "No", () => { window.history.back(); - }) - }else{ + }); + } else { options.addTitle("You are not allowed in this channel."); } - const html=float.generateHTML(); - html.classList.add("messagecontainer") + const html = float.generateHTML(); + html.classList.add("messagecontainer"); messages.append(html); - } - async getHTML(addstate=true){ - - if(addstate){ - history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); + async getHTML(addstate = true) { + if (addstate) { + history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id); } this.localuser.pageTitle("#" + this.name); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; - if(this.topic){ - channelTopic.innerHTML =""; - channelTopic.append(new MarkDown( - this.topic, - this - ).makeHTML()); + if (this.topic) { + channelTopic.innerHTML = ""; + channelTopic.append(new MarkDown(this.topic, this).makeHTML()); channelTopic.removeAttribute("hidden"); - }else channelTopic.setAttribute("hidden", ""); - if(this.guild !== this.localuser.lookingguild){ + } else channelTopic.setAttribute("hidden", ""); + if (this.guild !== this.localuser.lookingguild) { this.guild.loadGuild(); } - if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ + if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); } - if(this.myhtml){ + if (this.myhtml) { this.myhtml.classList.add("viewChannel"); } const id = ++Channel.genid; - if(this.localuser.channelfocus){ + if (this.localuser.channelfocus) { this.localuser.channelfocus.infinite.delete(); } this.guild.prevchannel = this; this.guild.perminfo.prevchannel = this.id; this.localuser.userinfo.updateLocal(); this.localuser.channelfocus = this; - //@ts-ignore another hack - if(this.nsfw&&(!this.perminfo.nsfwOk||!this.localuser.user.nsfw_allowed)){ + + if ( + this.nsfw && //@ts-ignore another hack + (!this.perminfo.nsfwOk || !this.localuser.user.nsfw_allowed) + ) { this.nsfwPannel(); return; } @@ -882,29 +925,30 @@ class Channel extends SnowFlake{ loading.classList.add("loading"); this.rendertyping(); this.localuser.getSidePannel(); - if(this.voice&&localStorage.getItem("Voice enabled")){ + if (this.voice && localStorage.getItem("Voice enabled")) { this.localuser.joinVoice(this); } - (document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage; - (document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + this.canMessage; + (document.getElementById("upload") as HTMLElement).style.visibility = this.canMessage + ? "visible" + : "hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "visible"; (document.getElementById("typebox") as HTMLDivElement).focus(); await this.putmessages(); await prom; - if(id !== Channel.genid){ + if (id !== Channel.genid) { return; } this.makereplybox(); await this.buildmessages(); //loading.classList.remove("loading"); - } typingmap: Map = new Map(); - async typingStart(typing: startTypingjson): Promise{ + async typingStart(typing: startTypingjson): Promise { const memb = await Member.new(typing.d.member!, this.guild); - if(!memb)return; - if(memb.id === this.localuser.user.id){ + if (!memb) return; + if (memb.id === this.localuser.user.id) { console.log("you is typing"); return; } @@ -913,58 +957,56 @@ class Channel extends SnowFlake{ setTimeout(this.rendertyping.bind(this), 10000); this.rendertyping(); } - similar(str:string){ - if(this.type===4) return -1; - const strl=Math.max(str.length,1) - if(this.name.includes(str)){ - return strl/this.name.length; - }else if(this.name.toLowerCase().includes(str.toLowerCase())){ - return strl/this.name.length/1.2; + similar(str: string) { + if (this.type === 4) return -1; + const strl = Math.max(str.length, 1); + if (this.name.includes(str)) { + return strl / this.name.length; + } else if (this.name.toLowerCase().includes(str.toLowerCase())) { + return strl / this.name.length / 1.2; } return 0; } - rendertyping(): void{ + rendertyping(): void { const typingtext = document.getElementById("typing") as HTMLDivElement; let build = ""; let showing = false; let i = 0; const curtime = Date.now() - 5000; - for(const thing of this.typingmap.keys()){ - if((this.typingmap.get(thing) as number) > curtime){ - if(i !== 0){ + for (const thing of this.typingmap.keys()) { + if ((this.typingmap.get(thing) as number) > curtime) { + if (i !== 0) { build += ", "; } i++; - if(thing.nick){ + if (thing.nick) { build += thing.nick; - }else{ + } else { build += thing.user.username; } showing = true; - }else{ + } else { this.typingmap.delete(thing); } } - build=I18n.getTranslation("typing",i+"",build); - if(this.localuser.channelfocus === this){ - if(showing){ + build = I18n.getTranslation("typing", i + "", build); + if (this.localuser.channelfocus === this) { + if (showing) { typingtext.classList.remove("hidden"); - const typingtext2 = document.getElementById( - "typingtext" - ) as HTMLDivElement; + const typingtext2 = document.getElementById("typingtext") as HTMLDivElement; typingtext2.textContent = build; - }else{ + } else { typingtext.classList.add("hidden"); } } } - static regenLoadingMessages(){ + static regenLoadingMessages() { const loading = document.getElementById("loadingdiv") as HTMLDivElement; loading.innerHTML = ""; - for(let i = 0; i < 15; i++){ + for (let i = 0; i < 15; i++) { const div = document.createElement("div"); div.classList.add("loadingmessage"); - if(Math.random() < 0.5){ + if (Math.random() < 0.5) { const pfp = document.createElement("div"); pfp.classList.add("loadingpfp"); const username = document.createElement("div"); @@ -981,36 +1023,50 @@ class Channel extends SnowFlake{ } } lastmessage: Message | undefined; - setnotifcation(){ - const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) - const options=["all", "onlyMentions", "none","default"].map(e=>I18n.getTranslation("guild."+e,defualt)); - const notiselect=new Dialog(""); - const form=notiselect.options.addForm("",(_,sent:any)=>{ - notiselect.hide(); - console.log(sent); - this.message_notifications = sent.channel_overrides[this.id].message_notifications; - },{ - fetchURL:`${this.info.api}/users/@me/guilds/${this.guild.id}/settings/`, - method:"PATCH", - headers:this.headers - }); - form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ - radio:true, - defaultIndex:this.message_notifications - },[0,1,2,3]); + setnotifcation() { + const defualt = I18n.getTranslation( + "guild." + ["all", "onlyMentions", "none", "default"][this.guild.message_notifications], + ); + const options = ["all", "onlyMentions", "none", "default"].map((e) => + I18n.getTranslation("guild." + e, defualt), + ); + const notiselect = new Dialog(""); + const form = notiselect.options.addForm( + "", + (_, sent: any) => { + notiselect.hide(); + console.log(sent); + this.message_notifications = sent.channel_overrides[this.id].message_notifications; + }, + { + fetchURL: `${this.info.api}/users/@me/guilds/${this.guild.id}/settings/`, + method: "PATCH", + headers: this.headers, + }, + ); + form.addSelect( + I18n.getTranslation("guild.selectnoti"), + "message_notifications", + options, + { + radio: true, + defaultIndex: this.message_notifications, + }, + [0, 1, 2, 3], + ); - form.addPreprocessor((e:any)=>{ - const message_notifications=e.message_notifications; + form.addPreprocessor((e: any) => { + const message_notifications = e.message_notifications; delete e.message_notifications; - e.channel_overrides={ - [this.id]:{ + e.channel_overrides = { + [this.id]: { message_notifications, - muted:this.muted, - mute_config:this.mute_config, - channel_id:this.id - } - } - }) + muted: this.muted, + mute_config: this.mute_config, + channel_id: this.id, + }, + }; + }); /* let noti = this.message_notifications; const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) @@ -1056,74 +1112,69 @@ class Channel extends SnowFlake{ */ notiselect.show(); } - async putmessages(){ + async putmessages() { //TODO swap out with the WS op code - if(this.allthewayup){ + if (this.allthewayup) { return; } - if(this.lastreadmessageid && this.messages.has(this.lastreadmessageid)){ + if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { return; } - const j = await fetch( - this.info.api + "/channels/" + this.id + "/messages?limit=100", - { - headers: this.headers, - } - ); + const j = await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100", { + headers: this.headers, + }); const response = await j.json(); - if(response.length !== 100){ + if (response.length !== 100) { this.allthewayup = true; } let prev: Message | undefined; - for(const thing of response){ + for (const thing of response) { const message = new Message(thing, this); - if(prev){ + if (prev) { this.idToNext.set(message.id, prev.id); this.idToPrev.set(prev.id, message.id); - }else{ + } else { this.lastmessage = message; this.lastmessageid = message.id; } prev = message; } } - delChannel(json: channeljson){ + delChannel(json: channeljson) { const build: Channel[] = []; - for(const thing of this.children){ - if(thing.id !== json.id){ + for (const thing of this.children) { + if (thing.id !== json.id) { build.push(thing); } } this.children = build; } - async grabAfter(id: string){ - if(id === this.lastmessage?.id){ + async grabAfter(id: string) { + if (id === this.lastmessage?.id) { return; } - await fetch( - this.info.api + "/channels/" +this.id +"/messages?limit=100&after=" +id,{ - headers: this.headers, - } - ) - .then(j=>{ + await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100&after=" + id, { + headers: this.headers, + }) + .then((j) => { return j.json(); }) - .then(response=>{ + .then((response) => { let previd: string = id; - for(const i in response){ + for (const i in response) { let messager: Message; let willbreak = false; - if(this.messages.has(response[i].id)){ + if (this.messages.has(response[i].id)) { messager = this.messages.get(response[i].id) as Message; willbreak = true; - }else{ + } else { messager = new Message(response[i], this); } this.idToPrev.set(messager.id, previd); this.idToNext.set(previd, messager.id); previd = messager.id; - if(willbreak){ + if (willbreak) { break; } } @@ -1131,36 +1182,33 @@ class Channel extends SnowFlake{ }); } topid!: string; - async grabBefore(id: string){ - if(this.topid && id === this.topid){ + async grabBefore(id: string) { + if (this.topid && id === this.topid) { return; } - await fetch( - this.info.api + "/channels/" + this.id +"/messages?before=" + id + "&limit=100", - { - headers: this.headers, - } - ) - .then(j=>{ + await fetch(this.info.api + "/channels/" + this.id + "/messages?before=" + id + "&limit=100", { + headers: this.headers, + }) + .then((j) => { return j.json(); }) - .then((response: messagejson[])=>{ - if(response.length < 100){ + .then((response: messagejson[]) => { + if (response.length < 100) { this.allthewayup = true; - if(response.length === 0){ + if (response.length === 0) { this.topid = id; } } let previd = id; - for(const i in response){ + for (const i in response) { let messager: Message; let willbreak = false; - if(this.messages.has(response[i].id)){ + if (this.messages.has(response[i].id)) { console.log("flaky"); messager = this.messages.get(response[i].id) as Message; willbreak = true; - }else{ + } else { messager = new Message(response[i], this); } @@ -1168,171 +1216,161 @@ class Channel extends SnowFlake{ this.idToPrev.set(previd, messager.id); previd = messager.id; - if(Number(i) === response.length - 1 && response.length < 100){ + if (Number(i) === response.length - 1 && response.length < 100) { this.topid = previd; } - if(willbreak){ + if (willbreak) { break; } } }); } /** - * Please dont use this, its not implemented. - * @deprecated - * @todo - **/ - async grabArround(/* id: string */){ + * Please dont use this, its not implemented. + * @deprecated + * @todo + **/ + async grabArround(/* id: string */) { //currently unused and no plans to use it yet throw new Error("please don't call this, no one has implemented it :P"); } - async buildmessages(){ + async buildmessages() { this.infinitefocus = false; await this.tryfocusinfinate(); } infinitefocus = false; - async tryfocusinfinate(){ - if(this.infinitefocus)return; + async tryfocusinfinate() { + if (this.infinitefocus) return; this.infinitefocus = true; const messages = document.getElementById("channelw") as HTMLDivElement; - const messageContainers = Array.from( - messages.getElementsByClassName("messagecontainer") - ); - for(const thing of messageContainers){ + const messageContainers = Array.from(messages.getElementsByClassName("messagecontainer")); + for (const thing of messageContainers) { thing.remove(); } const loading = document.getElementById("loadingdiv") as HTMLDivElement; const removetitle = document.getElementById("removetitle"); //messages.innerHTML=""; let id: string | undefined; - if(this.lastreadmessageid && this.messages.has(this.lastreadmessageid)){ + if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { id = this.lastreadmessageid; - }else if(this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))){ - - }else if(this.lastmessageid && this.messages.has(this.lastmessageid)){ + } else if (this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))) { + } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { id = this.goBackIds(this.lastmessageid, 50); } - if(!id){ - if(!removetitle){ + if (!id) { + if (!removetitle) { const title = document.createElement("h2"); title.id = "removetitle"; title.textContent = I18n.getTranslation("noMessages"); - title.classList.add("titlespace","messagecontainer"); + title.classList.add("titlespace", "messagecontainer"); messages.append(title); } this.infinitefocus = false; loading.classList.remove("loading"); return; - }else if(removetitle){ + } else if (removetitle) { removetitle.remove(); } - if(this.localuser.channelfocus !== this){ + if (this.localuser.channelfocus !== this) { return; } const elements = Array.from(messages.getElementsByClassName("scroller")); - for(const elm of elements){ + for (const elm of elements) { elm.remove(); console.warn("rouge element detected and removed"); } messages.append(await this.infinite.getDiv(id)); this.infinite.updatestuff(); - this.infinite.watchForChange().then(async _=>{ + this.infinite.watchForChange().then(async (_) => { //await new Promise(resolve => setTimeout(resolve, 0)); this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P loading.classList.remove("loading"); }); //this.infinite.focus(id.id,false); } - private goBackIds( - id: string, - back: number, - returnifnotexistant = true - ): string | undefined{ - while(back !== 0){ + private goBackIds(id: string, back: number, returnifnotexistant = true): string | undefined { + while (back !== 0) { const nextid = this.idToPrev.get(id); - if(nextid){ + if (nextid) { id = nextid; back--; - }else{ - if(returnifnotexistant){ + } else { + if (returnifnotexistant) { break; - }else{ + } else { return undefined; } } } return id; } - private findClosest(id: string | undefined){ - if(!this.lastmessageid || !id)return; + private findClosest(id: string | undefined) { + if (!this.lastmessageid || !id) return; let flake: string | undefined = this.lastmessageid; const time = SnowFlake.stringToUnixTime(id); let flaketime = SnowFlake.stringToUnixTime(flake); - while(flake && time < flaketime){ + while (flake && time < flaketime) { flake = this.idToPrev.get(flake); - if(!flake){ + if (!flake) { return; } flaketime = SnowFlake.stringToUnixTime(flake); } return flake; } - updateChannel(json: channeljson){ + updateChannel(json: channeljson) { this.type = json.type; this.name = json.name; const parent = this.localuser.channelids.get(json.parent_id); - if(parent){ + if (parent) { this.parent = parent; this.parent_id = parent.id; - }else{ + } else { this.parent = undefined; this.parent_id = undefined; } this.children = []; this.guild_id = json.guild_id; - const oldover=this.permission_overwrites; + 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"){ + this.permission_overwritesar = []; + for (const thing of json.permission_overwrites) { + if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { continue; } - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); + this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny)); const permisions = this.permission_overwrites.get(thing.id); - if(permisions){ + if (permisions) { const role = this.guild.roleids.get(thing.id); - if(role){ + if (role) { this.permission_overwritesar.push([role, permisions]); } } } - const nchange=[...new Set().union(oldover).difference(this.permission_overwrites)]; - const pchange=[...new Set().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) + const nchange = [...new Set().union(oldover).difference(this.permission_overwrites)]; + const pchange = [...new Set().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); + 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); + 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()){ + croleUpdate: (role: Role, perm: Permissions, added: boolean) => unknown = () => {}; + typingstart() { + if (this.typing > Date.now()) { return; } this.typing = Date.now() + 6000; @@ -1341,23 +1379,23 @@ class Channel extends SnowFlake{ headers: this.headers, }); } - get notification(){ + get notification() { let notinumber: number | null = this.message_notifications; - if(Number(notinumber) === 3){ + if (Number(notinumber) === 3) { notinumber = null; } notinumber ??= this.guild.message_notifications; - console.warn("info:",notinumber); - switch(Number(notinumber)){ + console.warn("info:", notinumber); + switch (Number(notinumber)) { case 0: - return"all"; + return "all"; case 1: - return"mentions"; + return "mentions"; case 2: - return"none"; + return "none"; case 3: default: - return"default"; + return "default"; } } async sendMessage( @@ -1365,23 +1403,23 @@ class Channel extends SnowFlake{ { attachments = [], replyingto = null, - }: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } - ){ + }: {attachments: Blob[]; embeds: embedjson; replyingto: Message | null}, + ) { let replyjson: any; - if(replyingto){ + if (replyingto) { replyjson = { guild_id: replyingto.guild.id, channel_id: replyingto.channel.id, message_id: replyingto.id, }; } - if(attachments.length === 0){ + if (attachments.length === 0) { const body = { content, nonce: Math.floor(Math.random() * 1000000000), message_reference: undefined, }; - if(replyjson){ + if (replyjson) { body.message_reference = replyjson; } return await fetch(this.info.api + "/channels/" + this.id + "/messages", { @@ -1389,108 +1427,102 @@ class Channel extends SnowFlake{ headers: this.headers, body: JSON.stringify(body), }); - }else{ + } else { const formData = new FormData(); const body = { content, nonce: Math.floor(Math.random() * 1000000000), message_reference: undefined, }; - if(replyjson){ + if (replyjson) { body.message_reference = replyjson; } formData.append("payload_json", JSON.stringify(body)); - for(const i in attachments){ + for (const i in attachments) { formData.append("files[" + i + "]", attachments[i]); } return await fetch(this.info.api + "/channels/" + this.id + "/messages", { method: "POST", body: formData, - headers: { Authorization: this.headers.Authorization }, + headers: {Authorization: this.headers.Authorization}, }); } } - unreads(){ - if(!this.hasunreads){ - if(this.myhtml){ - this.myhtml.classList.remove("cunread","mentioned"); + unreads() { + if (!this.hasunreads) { + if (this.myhtml) { + this.myhtml.classList.remove("cunread", "mentioned"); } - }else{ - if(this.myhtml){ + } else { + if (this.myhtml) { this.myhtml.classList.add("cunread"); } - if(this.mentions!==0){ - this.myhtml?.classList.add("mentioned") + if (this.mentions !== 0) { + this.myhtml?.classList.add("mentioned"); } } } - messageCreate(messagep: messageCreateJson): void{ - if(!this.hasPermission("VIEW_CHANNEL")){ + messageCreate(messagep: messageCreateJson): void { + if (!this.hasPermission("VIEW_CHANNEL")) { return; } const messagez = new Message(messagep.d, this); this.lastmessage = messagez; - if(this.lastmessageid){ + if (this.lastmessageid) { this.idToNext.set(this.lastmessageid, messagez.id); this.idToPrev.set(messagez.id, this.lastmessageid); } - if(messagez.mentionsuser(this.localuser.user)&&messagez.author!==this.localuser.user){ + if (messagez.mentionsuser(this.localuser.user) && messagez.author !== this.localuser.user) { this.mentions++; } this.lastmessageid = messagez.id; - if(messagez.author === this.localuser.user){ + if (messagez.author === this.localuser.user) { this.lastreadmessageid = messagez.id; } this.unreads(); this.guild.unreads(); - if(this === this.localuser.channelfocus){ - if(!this.infinitefocus){ + if (this === this.localuser.channelfocus) { + if (!this.infinitefocus) { this.tryfocusinfinate(); } this.infinite.addedBottom(); } - if(messagez.author === this.localuser.user){ + if (messagez.author === this.localuser.user) { return; } - if( - this.localuser.lookingguild?.prevchannel === this && document.hasFocus() - ){ + if (this.localuser.lookingguild?.prevchannel === this && document.hasFocus()) { return; } - if(this.notification === "all"){ + if (this.notification === "all") { this.notify(messagez); - }else if( - this.notification === "mentions" && messagez.mentionsuser(this.localuser.user) - ){ + } else if (this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)) { this.notify(messagez); } } - notititle(message: Message): string{ - return( - message.author.username + " > " + this.guild.properties.name + " > " + this.name - ); + notititle(message: Message): string { + return message.author.username + " > " + this.guild.properties.name + " > " + this.name; } - notify(message: Message, deep = 0){ - if(this.localuser.play){ - const voice=this.localuser.play.audios.get(this.localuser.getNotificationSound()); - if(voice){ + notify(message: Message, deep = 0) { + if (this.localuser.play) { + const voice = this.localuser.play.audios.get(this.localuser.getNotificationSound()); + if (voice) { voice.play(); } } - if(!("Notification" in window)){ - }else if(Notification.permission === "granted"){ + if (!("Notification" in window)) { + } else if (Notification.permission === "granted") { let noticontent: string | undefined | null = message.content.textContent; - if(message.embeds[0]){ + if (message.embeds[0]) { noticontent ||= message.embeds[0]?.json.title; noticontent ||= message.content.textContent; } noticontent ||= I18n.getTranslation("blankMessage"); let imgurl: null | string = null; const images = message.getimages(); - if(images.length){ + if (images.length) { const image = images[0]; - if(image.proxy_url){ + if (image.proxy_url) { imgurl ||= image.proxy_url; } imgurl ||= image.url; @@ -1500,61 +1532,53 @@ class Channel extends SnowFlake{ icon: message.author.getpfpsrc(this.guild), image: imgurl, }); - notification.addEventListener("click", _=>{ + notification.addEventListener("click", (_) => { window.focus(); this.getHTML(); }); - }else if(Notification.permission !== "denied"){ - Notification.requestPermission().then(()=>{ - if(deep === 3){ + } else if (Notification.permission !== "denied") { + Notification.requestPermission().then(() => { + if (deep === 3) { return; } this.notify(message, deep + 1); }); } } - async addRoleToPerms(role: Role){ - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + role.id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: "0", - deny: "0", - id: role.id, - type: 0, - }), - } - ); + async addRoleToPerms(role: Role) { + await fetch(this.info.api + "/channels/" + this.id + "/permissions/" + role.id, { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: "0", + deny: "0", + id: role.id, + type: 0, + }), + }); const perm = new Permissions("0", "0"); this.permission_overwrites.set(role.id, perm); this.permission_overwritesar.push([role, perm]); } - async updateRolePermissions(id: string, perms: Permissions){ + async updateRolePermissions(id: string, perms: Permissions) { const permission = this.permission_overwrites.get(id); - if(permission){ + if (permission) { permission.allow = perms.allow; permission.deny = perms.deny; - }else{ + } 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: perms.allow.toString(), - deny: perms.deny.toString(), - id, - type: 0, - }), - } - ); + await fetch(this.info.api + "/channels/" + this.id + "/permissions/" + id, { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: perms.allow.toString(), + deny: perms.deny.toString(), + id, + type: 0, + }), + }); } } Channel.setupcontextmenu(); -export{ Channel }; - - +export {Channel}; diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 794fd88e..fe738080 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,82 +1,82 @@ -import{ iOS }from"./utils/utils.js"; -class Contextmenu{ +import {iOS} from "./utils/utils.js"; +class Contextmenu { static currentmenu: HTMLElement | ""; name: string; buttons: [ - string|(()=>string), + string | (() => string), (this: x, arg: y, e: MouseEvent) => void, string | null, (this: x, arg: y) => boolean, (this: x, arg: y) => boolean, - string + string, ][]; div!: HTMLDivElement; - static setup(){ + static setup() { Contextmenu.currentmenu = ""; - document.addEventListener("click", event=>{ - if(Contextmenu.currentmenu === ""){ + document.addEventListener("click", (event) => { + if (Contextmenu.currentmenu === "") { return; } - if(!Contextmenu.currentmenu.contains(event.target as Node)){ + if (!Contextmenu.currentmenu.contains(event.target as Node)) { Contextmenu.currentmenu.remove(); Contextmenu.currentmenu = ""; } }); } - constructor(name: string){ + constructor(name: string) { this.name = name; this.buttons = []; } addbutton( - text: string|(()=>string), + text: string | (() => string), onclick: (this: x, arg: y, e: MouseEvent) => void, img: null | string = null, - shown: (this: x, arg: y) => boolean = _=>true, - enabled: (this: x, arg: y) => boolean = _=>true - ){ + shown: (this: x, arg: y) => boolean = (_) => true, + enabled: (this: x, arg: y) => boolean = (_) => true, + ) { this.buttons.push([text, onclick, img, shown, enabled, "button"]); - return{}; + return {}; } addsubmenu( - text: string|(()=>string), + text: string | (() => string), onclick: (this: x, arg: y, e: MouseEvent) => void, img = null, - shown: (this: x, arg: y) => boolean = _=>true, - enabled: (this: x, arg: y) => boolean = _=>true - ){ + shown: (this: x, arg: y) => boolean = (_) => true, + enabled: (this: x, arg: y) => boolean = (_) => true, + ) { this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); - return{}; + return {}; } - private makemenu(x: number, y: number, addinfo: x, other: y){ + private makemenu(x: number, y: number, addinfo: x, other: y) { const div = document.createElement("div"); div.classList.add("contextmenu", "flexttb"); let visibleButtons = 0; - for(const thing of this.buttons){ - if(!thing[3].call(addinfo, other))continue; + for (const thing of this.buttons) { + if (!thing[3].call(addinfo, other)) continue; visibleButtons++; const intext = document.createElement("button"); intext.disabled = !thing[4].call(addinfo, other); intext.classList.add("contextbutton"); - if(thing[0] instanceof Function){ + if (thing[0] instanceof Function) { intext.textContent = thing[0](); - }else{ + } else { intext.textContent = thing[0]; } console.log(thing); - if(thing[5] === "button" || thing[5] === "submenu"){ - intext.onclick = (e)=>{ + if (thing[5] === "button" || thing[5] === "submenu") { + intext.onclick = (e) => { div.remove(); - thing[1].call(addinfo, other,e) + thing[1].call(addinfo, other, e); }; } div.appendChild(intext); } - if(visibleButtons == 0)return; + if (visibleButtons == 0) return; - if(Contextmenu.currentmenu != ""){ + if (Contextmenu.currentmenu != "") { Contextmenu.currentmenu.remove(); } div.style.top = y + "px"; @@ -87,64 +87,74 @@ class Contextmenu{ Contextmenu.currentmenu = div; return this.div; } - bindContextmenu(obj: HTMLElement, addinfo: x, other: y,touchDrag:(x:number,y:number)=>unknown=()=>{},touchEnd:(x:number,y:number)=>unknown=()=>{}){ - const func = (event: MouseEvent)=>{ + bindContextmenu( + obj: HTMLElement, + addinfo: x, + other: y, + touchDrag: (x: number, y: number) => unknown = () => {}, + touchEnd: (x: number, y: number) => unknown = () => {}, + ) { + const func = (event: MouseEvent) => { event.preventDefault(); event.stopImmediatePropagation(); this.makemenu(event.clientX, event.clientY, addinfo, other); }; obj.addEventListener("contextmenu", func); - if(iOS){ - let hold:NodeJS.Timeout|undefined; - let x!:number; - let y!:number; - obj.addEventListener("touchstart",(event: TouchEvent)=>{ - x=event.touches[0].pageX; - y=event.touches[0].pageY; - if(event.touches.length > 1){ - event.preventDefault(); - event.stopImmediatePropagation(); - this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); - }else{ - // - event.stopImmediatePropagation(); - hold=setTimeout(()=>{ - if(lastx**2+lasty**2>10**2) return; + if (iOS) { + let hold: NodeJS.Timeout | undefined; + let x!: number; + let y!: number; + obj.addEventListener( + "touchstart", + (event: TouchEvent) => { + x = event.touches[0].pageX; + y = event.touches[0].pageY; + if (event.touches.length > 1) { + event.preventDefault(); + event.stopImmediatePropagation(); this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); - console.log(obj); - },500) - } - },{passive: false}); - let lastx=0; - let lasty=0; - obj.addEventListener("touchend",()=>{ - if(hold){ + } else { + // + event.stopImmediatePropagation(); + hold = setTimeout(() => { + if (lastx ** 2 + lasty ** 2 > 10 ** 2) return; + this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); + console.log(obj); + }, 500); + } + }, + {passive: false}, + ); + let lastx = 0; + let lasty = 0; + obj.addEventListener("touchend", () => { + if (hold) { clearTimeout(hold); } - touchEnd(lastx,lasty); + touchEnd(lastx, lasty); }); - obj.addEventListener("touchmove",(event)=>{ - lastx=event.touches[0].pageX-x; - lasty=event.touches[0].pageY-y; - touchDrag(lastx,lasty); + obj.addEventListener("touchmove", (event) => { + lastx = event.touches[0].pageX - x; + lasty = event.touches[0].pageY - y; + touchDrag(lastx, lasty); }); } return func; } - static keepOnScreen(obj: HTMLElement){ + static keepOnScreen(obj: HTMLElement) { const html = document.documentElement.getBoundingClientRect(); const docheight = html.height; const docwidth = html.width; const box = obj.getBoundingClientRect(); console.log(box, docheight, docwidth); - if(box.right > docwidth){ + if (box.right > docwidth) { console.log("test"); obj.style.left = docwidth - box.width + "px"; } - if(box.bottom > docheight){ + if (box.bottom > docheight) { obj.style.top = docheight - box.height + "px"; } } } Contextmenu.setup(); -export{ Contextmenu }; +export {Contextmenu}; diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 3cf2a18e..f803bad2 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -1,22 +1,22 @@ -import{ Guild }from"./guild.js"; -import{ Channel }from"./channel.js"; -import{ Message }from"./message.js"; -import{ Localuser }from"./localuser.js"; -import{ User }from"./user.js"; -import{channeljson,dirrectjson,memberjson,messagejson}from"./jsontypes.js"; -import{ Permissions }from"./permissions.js"; -import{ SnowFlake }from"./snowflake.js"; -import{ Contextmenu }from"./contextmenu.js"; -import { I18n } from "./i18n.js"; -import { Float, FormError } from "./settings.js"; +import {Guild} from "./guild.js"; +import {Channel} from "./channel.js"; +import {Message} from "./message.js"; +import {Localuser} from "./localuser.js"; +import {User} from "./user.js"; +import {channeljson, dirrectjson, memberjson, messagejson} from "./jsontypes.js"; +import {Permissions} from "./permissions.js"; +import {SnowFlake} from "./snowflake.js"; +import {Contextmenu} from "./contextmenu.js"; +import {I18n} from "./i18n.js"; +import {Float, FormError} from "./settings.js"; -class Direct extends Guild{ - declare channelids: { [key: string]: Group }; +class Direct extends Guild { + declare channelids: {[key: string]: Group}; channels: Group[]; - getUnixTime(): number{ + getUnixTime(): number { throw new Error("Do not call this for Direct, it does not make sense"); } - constructor(json: dirrectjson[], owner: Localuser){ + constructor(json: dirrectjson[], owner: Localuser) { super(-1, owner, null); this.message_notifications = 0; this.owner = owner; @@ -29,7 +29,7 @@ class Direct extends Guild{ this.roleids = new Map(); this.prevchannel = undefined; this.properties.name = I18n.getTranslation("DMs.name"); - for(const thing of json){ + for (const thing of json) { const temp = new Group(thing, this); this.channels.push(temp); this.channelids[temp.id] = temp; @@ -37,7 +37,7 @@ class Direct extends Guild{ } this.headchannels = this.channels; } - createChannelpac(json: any){ + createChannelpac(json: any) { const thischannel = new Group(json, this); this.channelids[thischannel.id] = thischannel; this.channels.push(thischannel); @@ -46,266 +46,270 @@ class Direct extends Guild{ this.printServers(); return thischannel; } - delChannel(json: channeljson){ + delChannel(json: channeljson) { const channel = this.channelids[json.id]; super.delChannel(json); - if(channel){ + if (channel) { channel.del(); } } - getHTML(){ - const ddiv=document.createElement("div"); - const build=super.getHTML(); - const freindDiv=document.createElement("div"); - freindDiv.classList.add("liststyle","flexltr","friendsbutton"); + getHTML() { + const ddiv = document.createElement("div"); + const build = super.getHTML(); + const freindDiv = document.createElement("div"); + freindDiv.classList.add("liststyle", "flexltr", "friendsbutton"); - const icon=document.createElement("span"); - icon.classList.add("svgicon","svg-friends","space"); + const icon = document.createElement("span"); + icon.classList.add("svgicon", "svg-friends", "space"); freindDiv.append(icon); freindDiv.append(I18n.getTranslation("friends.friends")); ddiv.append(freindDiv); - freindDiv.onclick=()=>{ + freindDiv.onclick = () => { this.loadChannel(null); - } + }; ddiv.append(build); return ddiv; } - noChannel(addstate:boolean){ - if(addstate){ - history.pushState([this.id,undefined], "", "/channels/" + this.id); + noChannel(addstate: boolean) { + if (addstate) { + history.pushState([this.id, undefined], "", "/channels/" + this.id); } this.localuser.pageTitle(I18n.getTranslation("friends.friendlist")); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; channelTopic.removeAttribute("hidden"); - channelTopic.textContent=""; + channelTopic.textContent = ""; const loading = document.getElementById("loadingdiv") as HTMLDivElement; loading.classList.remove("loading"); this.localuser.getSidePannel(); const messages = document.getElementById("channelw") as HTMLDivElement; - for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){ + for (const thing of Array.from(messages.getElementsByClassName("messagecontainer"))) { thing.remove(); } - const container=document.createElement("div"); - container.classList.add("messagecontainer","flexttb","friendcontainer") + const container = document.createElement("div"); + container.classList.add("messagecontainer", "flexttb", "friendcontainer"); messages.append(container); - const checkVoid=()=>{ - if(this.localuser.channelfocus!==undefined||this.localuser.lookingguild!==this){ - this.localuser.relationshipsUpdate=()=>{}; + const checkVoid = () => { + if (this.localuser.channelfocus !== undefined || this.localuser.lookingguild !== this) { + this.localuser.relationshipsUpdate = () => {}; } - } - function genuserstrip(user:User,icons:HTMLElement):HTMLElement{ - const div=document.createElement("div"); - div.classList.add("flexltr","liststyle"); + }; + function genuserstrip(user: User, icons: HTMLElement): HTMLElement { + const div = document.createElement("div"); + div.classList.add("flexltr", "liststyle"); user.bind(div); div.append(user.buildpfp()); - const userinfos=document.createElement("div"); + const userinfos = document.createElement("div"); userinfos.classList.add("flexttb"); - const username=document.createElement("span"); - username.textContent=user.name; - userinfos.append(username,user.getStatus()); + const username = document.createElement("span"); + username.textContent = user.name; + userinfos.append(username, user.getStatus()); div.append(userinfos); - User.contextmenu.bindContextmenu(div,user,undefined); - userinfos.style.flexGrow="1"; + User.contextmenu.bindContextmenu(div, user, undefined); + userinfos.style.flexGrow = "1"; div.append(icons); return div; } { //TODO update on users coming online - const online=document.createElement("button"); - online.textContent=I18n.getTranslation("friends.online"); + const online = document.createElement("button"); + online.textContent = I18n.getTranslation("friends.online"); channelTopic.append(online); - const genOnline=()=>{ - this.localuser.relationshipsUpdate=genOnline; + const genOnline = () => { + this.localuser.relationshipsUpdate = genOnline; checkVoid(); - container.innerHTML=""; + container.innerHTML = ""; container.append(I18n.getTranslation("friends.online:")); - for(const user of this.localuser.inrelation){ - if(user.relationshipType===1&&user.online){ - const buttonc=document.createElement("div"); - const button1=document.createElement("span"); - button1.classList.add("svg-frmessage","svgicon"); + for (const user of this.localuser.inrelation) { + if (user.relationshipType === 1 && user.online) { + const buttonc = document.createElement("div"); + const button1 = document.createElement("span"); + button1.classList.add("svg-frmessage", "svgicon"); buttonc.append(button1); buttonc.classList.add("friendlyButton"); - buttonc.onclick=(e)=>{ + buttonc.onclick = (e) => { e.stopImmediatePropagation(); user.opendm(); - } - container.append(genuserstrip(user,buttonc)); + }; + container.append(genuserstrip(user, buttonc)); } } - } - online.onclick=genOnline; + }; + online.onclick = genOnline; genOnline(); } { - const all=document.createElement("button"); - all.textContent=I18n.getTranslation("friends.all"); - const genAll=()=>{ - this.localuser.relationshipsUpdate=genAll; + const all = document.createElement("button"); + all.textContent = I18n.getTranslation("friends.all"); + const genAll = () => { + this.localuser.relationshipsUpdate = genAll; checkVoid(); - container.innerHTML=""; + container.innerHTML = ""; container.append(I18n.getTranslation("friends.all:")); - for(const user of this.localuser.inrelation){ - if(user.relationshipType===1){ - const buttonc=document.createElement("div"); - const button1=document.createElement("span"); - button1.classList.add("svg-frmessage","svgicon"); + for (const user of this.localuser.inrelation) { + if (user.relationshipType === 1) { + const buttonc = document.createElement("div"); + const button1 = document.createElement("span"); + button1.classList.add("svg-frmessage", "svgicon"); buttonc.append(button1); buttonc.classList.add("friendlyButton"); - buttonc.onclick=(e)=>{ + buttonc.onclick = (e) => { e.stopImmediatePropagation(); user.opendm(); - } - container.append(genuserstrip(user,buttonc)); + }; + container.append(genuserstrip(user, buttonc)); } } - } - all.onclick=genAll; + }; + all.onclick = genAll; channelTopic.append(all); } { - const pending=document.createElement("button"); - pending.textContent=I18n.getTranslation("friends.pending"); - const genPending=()=>{ - this.localuser.relationshipsUpdate=genPending; + const pending = document.createElement("button"); + pending.textContent = I18n.getTranslation("friends.pending"); + const genPending = () => { + this.localuser.relationshipsUpdate = genPending; checkVoid(); - container.innerHTML=""; + container.innerHTML = ""; container.append(I18n.getTranslation("friends.pending:")); - for(const user of this.localuser.inrelation){ - if(user.relationshipType===3||user.relationshipType===4){ - const buttons=document.createElement("div"); + for (const user of this.localuser.inrelation) { + if (user.relationshipType === 3 || user.relationshipType === 4) { + const buttons = document.createElement("div"); buttons.classList.add("flexltr"); - const buttonc=document.createElement("div"); - const button1=document.createElement("span"); - button1.classList.add("svgicon","svg-x"); - if(user.relationshipType===3){ - const buttonc=document.createElement("div"); - const button2=document.createElement("span"); - button2.classList.add("svgicon","svg-x"); + const buttonc = document.createElement("div"); + const button1 = document.createElement("span"); + button1.classList.add("svgicon", "svg-x"); + if (user.relationshipType === 3) { + const buttonc = document.createElement("div"); + const button2 = document.createElement("span"); + button2.classList.add("svgicon", "svg-x"); button2.classList.add("svg-addfriend"); buttonc.append(button2); buttonc.classList.add("friendlyButton"); buttonc.append(button2); buttons.append(buttonc); - buttonc.onclick=(e)=>{ + buttonc.onclick = (e) => { e.stopImmediatePropagation(); user.changeRelationship(1); outerDiv.remove(); - } + }; } buttonc.append(button1); buttonc.classList.add("friendlyButton"); - buttonc.onclick=(e)=>{ + buttonc.onclick = (e) => { e.stopImmediatePropagation(); user.changeRelationship(0); outerDiv.remove(); - } + }; buttons.append(buttonc); - const outerDiv=genuserstrip(user,buttons); + const outerDiv = genuserstrip(user, buttons); container.append(outerDiv); } } - } - pending.onclick=genPending; + }; + pending.onclick = genPending; channelTopic.append(pending); } { - const blocked=document.createElement("button"); - blocked.textContent=I18n.getTranslation("friends.blocked"); + const blocked = document.createElement("button"); + blocked.textContent = I18n.getTranslation("friends.blocked"); - const genBlocked=()=>{ - this.localuser.relationshipsUpdate=genBlocked; + const genBlocked = () => { + this.localuser.relationshipsUpdate = genBlocked; checkVoid(); - container.innerHTML=""; + container.innerHTML = ""; container.append(I18n.getTranslation("friends.blockedusers")); - for(const user of this.localuser.inrelation){ - if(user.relationshipType===2){ - const buttonc=document.createElement("div"); - const button1=document.createElement("span"); - button1.classList.add("svg-x","svgicon"); + for (const user of this.localuser.inrelation) { + if (user.relationshipType === 2) { + const buttonc = document.createElement("div"); + const button1 = document.createElement("span"); + button1.classList.add("svg-x", "svgicon"); buttonc.append(button1); buttonc.classList.add("friendlyButton"); - buttonc.onclick=(e)=>{ + buttonc.onclick = (e) => { user.changeRelationship(0); e.stopImmediatePropagation(); outerDiv.remove(); - } - const outerDiv=genuserstrip(user,buttonc); + }; + const outerDiv = genuserstrip(user, buttonc); container.append(outerDiv); } } - } - blocked.onclick=genBlocked; + }; + blocked.onclick = genBlocked; channelTopic.append(blocked); } { - const add=document.createElement("button"); - add.textContent=I18n.getTranslation("friends.addfriend"); - add.onclick=()=>{ - this.localuser.relationshipsUpdate=()=>{}; - container.innerHTML=""; - const float=new Float(""); - const options=float.options; - const form=options.addForm("",(e:any)=>{ - console.log(e); - if(e.code===404){ - throw new FormError(text,I18n.getTranslation("friends.notfound")); - }else if(e.code===400){ - throw new FormError(text,e.message.split("Error: ")[1]); - }else{ - const box=text.input.deref(); - if(!box)return; - box.value=""; - } - },{ - method:"POST", - fetchURL:this.info.api+"/users/@me/relationships", - headers:this.headers - }); - const text=form.addTextInput(I18n.getTranslation("friends.addfriendpromt"),"username"); - form.addPreprocessor((obj:any)=>{ - const [username,discriminator]=obj.username.split("#"); - obj.username=username; - obj.discriminator=discriminator; - if(!discriminator){ - throw new FormError(text,I18n.getTranslation("friends.discnotfound")); + const add = document.createElement("button"); + add.textContent = I18n.getTranslation("friends.addfriend"); + add.onclick = () => { + this.localuser.relationshipsUpdate = () => {}; + container.innerHTML = ""; + const float = new Float(""); + const options = float.options; + const form = options.addForm( + "", + (e: any) => { + console.log(e); + if (e.code === 404) { + throw new FormError(text, I18n.getTranslation("friends.notfound")); + } else if (e.code === 400) { + throw new FormError(text, e.message.split("Error: ")[1]); + } else { + const box = text.input.deref(); + if (!box) return; + box.value = ""; + } + }, + { + method: "POST", + fetchURL: this.info.api + "/users/@me/relationships", + headers: this.headers, + }, + ); + const text = form.addTextInput(I18n.getTranslation("friends.addfriendpromt"), "username"); + form.addPreprocessor((obj: any) => { + const [username, discriminator] = obj.username.split("#"); + obj.username = username; + obj.discriminator = discriminator; + if (!discriminator) { + throw new FormError(text, I18n.getTranslation("friends.discnotfound")); } }); container.append(float.generateHTML()); - } + }; channelTopic.append(add); } } - get mentions(){ - let mentions=0; - for(const thing of this.localuser.inrelation){ - if(thing.relationshipType===3){ - mentions+=1; + get mentions() { + let mentions = 0; + for (const thing of this.localuser.inrelation) { + if (thing.relationshipType === 3) { + mentions += 1; } } return mentions; } - giveMember(_member: memberjson){ + giveMember(_member: memberjson) { throw new Error("not a real guild, can't give member object"); } - getRole(/* ID: string */){ + getRole(/* ID: string */) { return null; } - hasRole(/* r: string */){ + hasRole(/* r: string */) { return false; } - isAdmin(){ + isAdmin() { return false; } - unreaddms(){ - for(const thing of this.channels){ + unreaddms() { + for (const thing of this.channels) { (thing as Group).unreads(); } } @@ -335,34 +339,46 @@ dmPermissions.setPermission("STREAM", 1); dmPermissions.setPermission("USE_VAD", 1); // @ts-ignore I need to look into this lol -class Group extends Channel{ +class Group extends Channel { user: User; static contextmenu = new Contextmenu("channel menu"); - static setupcontextmenu(){ - this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.copyId"), function(this: Group){ - navigator.clipboard.writeText(this.id); - }); + static setupcontextmenu() { + this.contextmenu.addbutton( + () => I18n.getTranslation("DMs.copyId"), + function (this: Group) { + navigator.clipboard.writeText(this.id); + }, + ); - this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.markRead"), function(this: Group){ - this.readbottom(); - }); + this.contextmenu.addbutton( + () => I18n.getTranslation("DMs.markRead"), + function (this: Group) { + this.readbottom(); + }, + ); - this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.close"), function(this: Group){ - this.deleteChannel(); - }); + this.contextmenu.addbutton( + () => I18n.getTranslation("DMs.close"), + function (this: Group) { + this.deleteChannel(); + }, + ); - this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(){ - navigator.clipboard.writeText(this.user.id); - }); + this.contextmenu.addbutton( + () => I18n.getTranslation("user.copyId"), + function () { + navigator.clipboard.writeText(this.user.id); + }, + ); } - constructor(json: dirrectjson, owner: Direct){ + constructor(json: dirrectjson, owner: Direct) { super(-1, owner, json.id); this.owner = owner; this.headers = this.guild.headers; this.name = json.recipients[0]?.username; - if(json.recipients[0]){ + if (json.recipients[0]) { this.user = new User(json.recipients[0], this.localuser); - }else{ + } else { this.user = this.localuser.user; } this.name ??= this.localuser.user.username; @@ -376,26 +392,26 @@ class Group extends Channel{ this.setUpInfiniteScroller(); this.updatePosition(); } - updatePosition(){ - if(this.lastmessageid){ + updatePosition() { + if (this.lastmessageid) { this.position = SnowFlake.stringToUnixTime(this.lastmessageid); - }else{ + } else { this.position = 0; } this.position = -Math.max(this.position, this.getUnixTime()); } - createguildHTML(){ + createguildHTML() { const div = document.createElement("div"); - Group.contextmenu.bindContextmenu(div, this,undefined); + Group.contextmenu.bindContextmenu(div, this, undefined); this.html = new WeakRef(div); - div.classList.add("flexltr","liststyle"); + div.classList.add("flexltr", "liststyle"); const myhtml = document.createElement("span"); myhtml.classList.add("ellipsis"); myhtml.textContent = this.name; div.appendChild(this.user.buildpfp()); div.appendChild(myhtml); (div as any).myinfo = this; - div.onclick = _=>{ + div.onclick = (_) => { this.getHTML(); const toggle = document.getElementById("maintoggle") as HTMLInputElement; toggle.checked = true; @@ -403,56 +419,55 @@ class Group extends Channel{ return div; } - async getHTML(addstate=true){ + async getHTML(addstate = true) { const id = ++Channel.genid; - if(this.localuser.channelfocus){ + if (this.localuser.channelfocus) { this.localuser.channelfocus.infinite.delete(); } - if(this.guild !== this.localuser.lookingguild){ + if (this.guild !== this.localuser.lookingguild) { this.guild.loadGuild(); } this.guild.prevchannel = this; this.localuser.channelfocus = this; const prom = this.infinite.delete(); - if(addstate){ - history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); + if (addstate) { + history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id); } this.localuser.pageTitle("@" + this.name); - (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); + (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden", ""); const loading = document.getElementById("loadingdiv") as HTMLDivElement; Channel.regenLoadingMessages(); loading.classList.add("loading"); this.rendertyping(); - (document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; - (document.getElementById("upload") as HTMLElement).style.visibility="visible"; - (document.getElementById("typediv") as HTMLElement).style.visibility="visible"; + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + true; + (document.getElementById("upload") as HTMLElement).style.visibility = "visible"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "visible"; (document.getElementById("typebox") as HTMLDivElement).focus(); await this.putmessages(); await prom; this.localuser.getSidePannel(); - if(id !== Channel.genid){ + if (id !== Channel.genid) { return; } this.buildmessages(); - } - messageCreate(messagep: { d: messagejson }){ + messageCreate(messagep: {d: messagejson}) { this.mentions++; const messagez = new Message(messagep.d, this); - if(this.lastmessageid){ + if (this.lastmessageid) { this.idToNext.set(this.lastmessageid, messagez.id); this.idToPrev.set(messagez.id, this.lastmessageid); } this.lastmessageid = messagez.id; - if(messagez.author === this.localuser.user){ + if (messagez.author === this.localuser.user) { this.lastreadmessageid = messagez.id; - if(this.myhtml){ + if (this.myhtml) { this.myhtml.classList.remove("cunread"); } - }else{ - if(this.myhtml){ + } else { + if (this.myhtml) { this.myhtml.classList.add("cunread"); } } @@ -460,55 +475,55 @@ class Group extends Channel{ this.updatePosition(); this.infinite.addedBottom(); this.guild.sortchannels(); - if(this.myhtml){ + if (this.myhtml) { const parrent = this.myhtml.parentElement as HTMLElement; parrent.prepend(this.myhtml); } - if(this === this.localuser.channelfocus){ - if(!this.infinitefocus){ + if (this === this.localuser.channelfocus) { + if (!this.infinitefocus) { this.tryfocusinfinate(); } this.infinite.addedBottom(); } this.unreads(); - if(messagez.author === this.localuser.user){ - this.mentions=0; + if (messagez.author === this.localuser.user) { + this.mentions = 0; return; } - if(this.localuser.lookingguild?.prevchannel === this && document.hasFocus()){ + if (this.localuser.lookingguild?.prevchannel === this && document.hasFocus()) { return; } - if(this.notification === "all"){ + if (this.notification === "all") { this.notify(messagez); - }else if(this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)){ + } else if (this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)) { this.notify(messagez); } } - notititle(message: Message){ + notititle(message: Message) { return message.author.username; } - readbottom(){ + readbottom() { super.readbottom(); this.unreads(); } all: WeakRef = new WeakRef(document.createElement("div")); noti: WeakRef = new WeakRef(document.createElement("div")); - del(){ + del() { const all = this.all.deref(); - if(all){ + if (all) { all.remove(); } - if(this.myhtml){ + if (this.myhtml) { this.myhtml.remove(); } } - unreads(){ + unreads() { const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime const current = this.all.deref(); - if(this.hasunreads){ + if (this.hasunreads) { { const noti = this.noti.deref(); - if(noti){ + if (noti) { noti.textContent = this.mentions + ""; return; } @@ -525,24 +540,24 @@ class Group extends Channel{ buildpfp.classList.add("mentioned"); div.append(buildpfp); sentdms.append(div); - div.onclick = _=>{ + div.onclick = (_) => { this.guild.loadGuild(); this.getHTML(); const toggle = document.getElementById("maintoggle") as HTMLInputElement; toggle.checked = true; }; - }else if(current){ + } else if (current) { current.remove(); - }else{ + } else { } } - isAdmin(): boolean{ + isAdmin(): boolean { return false; } - hasPermission(name: string): boolean{ + hasPermission(name: string): boolean { return dmPermissions.hasPermission(name); } } -export{ Direct, Group }; +export {Direct, Group}; -Group.setupcontextmenu() +Group.setupcontextmenu(); diff --git a/src/webpage/disimg.ts b/src/webpage/disimg.ts index a1d2a1d4..e37978e3 100644 --- a/src/webpage/disimg.ts +++ b/src/webpage/disimg.ts @@ -1,37 +1,37 @@ -class ImagesDisplay{ - images:string[]; - index=0; - constructor(srcs:string[],index=0){ - this.images=srcs; - this.index=index; +class ImagesDisplay { + images: string[]; + index = 0; + constructor(srcs: string[], index = 0) { + this.images = srcs; + this.index = index; } - weakbg=new WeakRef(document.createElement("div")); - get background():HTMLElement|undefined{ + weakbg = new WeakRef(document.createElement("div")); + get background(): HTMLElement | undefined { return this.weakbg.deref(); } - set background(e:HTMLElement){ - this.weakbg=new WeakRef(e); + set background(e: HTMLElement) { + this.weakbg = new WeakRef(e); } - makeHTML():HTMLElement{ + makeHTML(): HTMLElement { //TODO this should be able to display more than one image at a time lol - const image= document.createElement("img"); - image.src=this.images[this.index]; - image.classList.add("imgfit","centeritem"); + const image = document.createElement("img"); + image.src = this.images[this.index]; + image.classList.add("imgfit", "centeritem"); return image; } - show(){ + show() { this.background = document.createElement("div"); this.background.classList.add("background"); this.background.appendChild(this.makeHTML()); - this.background.onclick = _=>{ + this.background.onclick = (_) => { this.hide(); }; document.body.append(this.background); } - hide(){ - if(this.background){ + hide() { + if (this.background) { this.background.remove(); } } } -export{ImagesDisplay} +export {ImagesDisplay}; diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index 73bad4a2..50dd6816 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -1,94 +1,85 @@ -import{ Message }from"./message.js"; -import{ MarkDown }from"./markdown.js"; -import{ embedjson, invitejson }from"./jsontypes.js"; -import{ getapiurls, getInstances }from"./utils/utils.js"; -import{ Guild }from"./guild.js"; -import { I18n } from "./i18n.js"; -import { ImagesDisplay } from "./disimg.js"; +import {Message} from "./message.js"; +import {MarkDown} from "./markdown.js"; +import {embedjson, invitejson} from "./jsontypes.js"; +import {getapiurls, getInstances} from "./utils/utils.js"; +import {Guild} from "./guild.js"; +import {I18n} from "./i18n.js"; +import {ImagesDisplay} from "./disimg.js"; -class Embed{ +class Embed { type: string; owner: Message; json: embedjson; - constructor(json: embedjson, owner: Message){ + constructor(json: embedjson, owner: Message) { this.type = this.getType(json); this.owner = owner; this.json = json; } - getType(json: embedjson){ + getType(json: embedjson) { const instances = getInstances(); - if( - instances && -json.type === "link" && -json.url && -URL.canParse(json.url) - ){ + if (instances && json.type === "link" && json.url && URL.canParse(json.url)) { const Url = new URL(json.url); - for(const instance of instances){ - if(instance.url && URL.canParse(instance.url)){ + for (const instance of instances) { + if (instance.url && URL.canParse(instance.url)) { const IUrl = new URL(instance.url); const params = new URLSearchParams(Url.search); let host: string; - if(params.has("instance")){ + if (params.has("instance")) { const url = params.get("instance") as string; - if(URL.canParse(url)){ + if (URL.canParse(url)) { host = new URL(url).host; - }else{ + } else { host = Url.host; } - }else{ + } else { host = Url.host; } - if(IUrl.host === host){ - const code = -Url.pathname.split("/")[Url.pathname.split("/").length - 1]; + if (IUrl.host === host) { + const code = Url.pathname.split("/")[Url.pathname.split("/").length - 1]; json.invite = { url: instance.url, code, }; - return"invite"; + return "invite"; } } } } return json.type || "rich"; } - generateHTML(){ - switch(this.type){ - case"rich": - return this.generateRich(); - case"image": - return this.generateImage(); - case"invite": - return this.generateInvite(); - case"link": - return this.generateLink(); - case"video": - case"article": - return this.generateArticle(); - default: - console.warn( - `unsupported embed type ${this.type}, please add support dev :3`, - this.json - ); - return document.createElement("div"); //prevent errors by giving blank div + generateHTML() { + switch (this.type) { + case "rich": + return this.generateRich(); + case "image": + return this.generateImage(); + case "invite": + return this.generateInvite(); + case "link": + return this.generateLink(); + case "video": + case "article": + return this.generateArticle(); + default: + console.warn(`unsupported embed type ${this.type}, please add support dev :3`, this.json); + return document.createElement("div"); //prevent errors by giving blank div } } - get message(){ + get message() { return this.owner; } - get channel(){ + get channel() { return this.message.channel; } - get guild(){ + get guild() { return this.channel.guild; } - get localuser(){ + get localuser() { return this.guild.localuser; } - generateRich(){ + generateRich() { const div = document.createElement("div"); - if(this.json.color){ + if (this.json.color) { div.style.backgroundColor = "#" + this.json.color.toString(16); } div.classList.add("embed-color"); @@ -97,9 +88,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; embed.classList.add("embed"); div.append(embed); - if(this.json.author){ + if (this.json.author) { const authorline = document.createElement("div"); - if(this.json.author.icon_url){ + if (this.json.author.icon_url) { const img = document.createElement("img"); img.classList.add("embedimg"); img.src = this.json.author.icon_url; @@ -107,31 +98,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; } const a = document.createElement("a"); a.textContent = this.json.author.name as string; - if(this.json.author.url){ + if (this.json.author.url) { MarkDown.safeLink(a, this.json.author.url); } a.classList.add("username"); authorline.append(a); embed.append(authorline); } - if(this.json.title){ + if (this.json.title) { const title = document.createElement("a"); title.append(new MarkDown(this.json.title, this.channel).makeHTML()); - if(this.json.url){ + if (this.json.url) { MarkDown.safeLink(title, this.json.url); } title.classList.add("embedtitle"); embed.append(title); } - if(this.json.description){ + if (this.json.description) { const p = document.createElement("p"); p.append(new MarkDown(this.json.description, this.channel).makeHTML()); embed.append(p); } embed.append(document.createElement("br")); - if(this.json.fields){ - for(const thing of this.json.fields){ + if (this.json.fields) { + for (const thing of this.json.fields) { const div = document.createElement("div"); const b = document.createElement("b"); b.textContent = thing.name; @@ -141,31 +132,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; p.classList.add("embedp"); div.append(p); - if(thing.inline){ + if (thing.inline) { div.classList.add("inline"); } embed.append(div); } } - if(this.json.footer || this.json.timestamp){ + if (this.json.footer || this.json.timestamp) { const footer = document.createElement("div"); - if(this.json?.footer?.icon_url){ + if (this.json?.footer?.icon_url) { const img = document.createElement("img"); img.src = this.json.footer.icon_url; img.classList.add("embedicon"); footer.append(img); } - if(this.json?.footer?.text){ + if (this.json?.footer?.text) { const span = document.createElement("span"); span.textContent = this.json.footer.text; footer.append(span); } - if(this.json?.footer && this.json?.timestamp){ + if (this.json?.footer && this.json?.timestamp) { const span = document.createElement("span"); span.textContent = " • "; footer.append(span); } - if(this.json?.timestamp){ + if (this.json?.timestamp) { const span = document.createElement("span"); span.textContent = new Date(this.json.timestamp).toLocaleString(); footer.append(span); @@ -174,15 +165,15 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; } return div; } - generateImage(){ + generateImage() { const img = document.createElement("img"); img.classList.add("messageimg"); - img.onclick = function(){ + img.onclick = function () { const full = new ImagesDisplay([img.src]); full.show(); }; img.src = this.json.thumbnail.proxy_url; - if(this.json.thumbnail.width){ + if (this.json.thumbnail.width) { let scale = 1; const max = 96 * 3; scale = Math.max(scale, this.json.thumbnail.width / max); @@ -195,12 +186,12 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; console.log(this.json, "Image fix"); return img; } - generateLink(){ + generateLink() { const table = document.createElement("table"); table.classList.add("embed", "linkembed"); const trtop = document.createElement("tr"); table.append(trtop); - if(this.json.url && this.json.title){ + if (this.json.url && this.json.title) { const td = document.createElement("td"); const a = document.createElement("a"); MarkDown.safeLink(a, this.json.url); @@ -211,9 +202,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; { const td = document.createElement("td"); const img = document.createElement("img"); - if(this.json.thumbnail){ + if (this.json.thumbnail) { img.classList.add("embedimg"); - img.onclick = function(){ + img.onclick = function () { const full = new ImagesDisplay([img.src]); full.show(); }; @@ -224,7 +215,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; } const bottomtr = document.createElement("tr"); const td = document.createElement("td"); - if(this.json.description){ + if (this.json.description) { const span = document.createElement("span"); span.textContent = this.json.description; td.append(span); @@ -233,59 +224,62 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1]; table.append(bottomtr); return table; } - invcache: [invitejson, { cdn: string; api: string }] | undefined; - generateInvite(){ - if(this.invcache && (!this.json.invite || !this.localuser)){ + invcache: [invitejson, {cdn: string; api: string}] | undefined; + generateInvite() { + if (this.invcache && (!this.json.invite || !this.localuser)) { return this.generateLink(); } const div = document.createElement("div"); div.classList.add("embed", "inviteEmbed", "flexttb"); const json1 = this.json.invite; - (async ()=>{ + (async () => { let json: invitejson; - let info: { cdn: string; api: string }; - if(!this.invcache){ - if(!json1){ - div.classList.remove("embed", "inviteEmbed", "flexttb") + let info: {cdn: string; api: string}; + if (!this.invcache) { + if (!json1) { + div.classList.remove("embed", "inviteEmbed", "flexttb"); div.append(this.generateLink()); return; } const tempinfo = await getapiurls(json1.url); - if(!tempinfo){ - div.classList.remove("embed", "inviteEmbed", "flexttb") + if (!tempinfo) { + div.classList.remove("embed", "inviteEmbed", "flexttb"); div.append(this.generateLink()); return; } info = tempinfo; const res = await fetch(info.api + "/invites/" + json1.code); - if(!res.ok){ - div.classList.remove("embed", "inviteEmbed", "flexttb") + if (!res.ok) { + div.classList.remove("embed", "inviteEmbed", "flexttb"); div.append(this.generateLink()); return; } json = (await res.json()) as invitejson; this.invcache = [json, info]; - }else{ + } else { [json, info] = this.invcache; } - if(!json){ + if (!json) { div.append(this.generateLink()); - div.classList.remove("embed", "inviteEmbed", "flexttb") + div.classList.remove("embed", "inviteEmbed", "flexttb"); return; } - if(json.guild.banner){ + if (json.guild.banner) { const banner = document.createElement("img"); - banner.src = this.localuser.info.cdn + "/icons/" + json.guild.id + "/" + json.guild.banner + ".png?size=256"; + banner.src = + this.localuser.info.cdn + + "/icons/" + + json.guild.id + + "/" + + json.guild.banner + + ".png?size=256"; banner.classList.add("banner"); div.append(banner); } - const guild: invitejson["guild"] & { info?: { cdn: string } } = -json.guild; + const guild: invitejson["guild"] & {info?: {cdn: string}} = json.guild; guild.info = info; - const icon = Guild.generateGuildIcon( -guild as invitejson["guild"] & { info: { cdn: string } } - ); + const icon = Guild.generateGuildIcon(guild as invitejson["guild"] & {info: {cdn: string}}); const iconrow = document.createElement("div"); iconrow.classList.add("flexltr"); iconrow.append(icon); @@ -305,30 +299,30 @@ guild as invitejson["guild"] & { info: { cdn: string } } div.append(iconrow); const h2 = document.createElement("h2"); - h2.textContent = I18n.getTranslation("invite.invitedBy",json.inviter.username); + h2.textContent = I18n.getTranslation("invite.invitedBy", json.inviter.username); div.append(h2); const button = document.createElement("button"); button.textContent = I18n.getTranslation("invite.accept"); - if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){ + if (this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)) { button.textContent = I18n.getTranslation("invite.alreadyJoined"); button.disabled = true; } button.classList.add("acceptinvbutton"); div.append(button); - button.onclick = _=>{ - if(this.localuser.info.api.startsWith(info.api)){ + button.onclick = (_) => { + if (this.localuser.info.api.startsWith(info.api)) { fetch(this.localuser.info.api + "/invites/" + json.code, { method: "POST", headers: this.localuser.headers, }) - .then(r=>r.json()) - .then(_=>{ - if(_.message){ + .then((r) => r.json()) + .then((_) => { + if (_.message) { alert(_.message); } }); - }else{ - if(this.json.invite){ + } else { + if (this.json.invite) { const params = new URLSearchParams(""); params.set("instance", this.json.invite.url); const encoded = params.toString(); @@ -340,33 +334,33 @@ guild as invitejson["guild"] & { info: { cdn: string } } })(); return div; } - generateArticle(){ + generateArticle() { const colordiv = document.createElement("div"); colordiv.style.backgroundColor = "#000000"; colordiv.classList.add("embed-color"); const div = document.createElement("div"); div.classList.add("embed"); - if(this.json.provider){ + if (this.json.provider) { const provider = document.createElement("p"); provider.classList.add("provider"); provider.textContent = this.json.provider.name; div.append(provider); } const a = document.createElement("a"); - if(this.json.url && this.json.url){ + if (this.json.url && this.json.url) { MarkDown.safeLink(a, this.json.url); a.textContent = this.json.url; div.append(a); } - if(this.json.description){ + if (this.json.description) { const description = document.createElement("p"); description.textContent = this.json.description; div.append(description); } - if(this.json.thumbnail){ + if (this.json.thumbnail) { const img = document.createElement("img"); - if(this.json.thumbnail.width && this.json.thumbnail.width){ + if (this.json.thumbnail.width && this.json.thumbnail.width) { let scale = 1; const inch = 96; scale = Math.max(scale, this.json.thumbnail.width / inch / 4); @@ -377,21 +371,21 @@ guild as invitejson["guild"] & { info: { cdn: string } } img.style.height = this.json.thumbnail.height + "px"; } img.classList.add("bigembedimg"); - if(this.json.video){ - img.onclick = async ()=>{ - if(this.json.video){ + if (this.json.video) { + img.onclick = async () => { + if (this.json.video) { img.remove(); const iframe = document.createElement("iframe"); iframe.src = this.json.video.url + "?autoplay=1"; - if(this.json.thumbnail.width && this.json.thumbnail.width){ + if (this.json.thumbnail.width && this.json.thumbnail.width) { iframe.style.width = this.json.thumbnail.width + "px"; iframe.style.height = this.json.thumbnail.height + "px"; } div.append(iframe); } }; - }else{ - img.onclick = async ()=>{ + } else { + img.onclick = async () => { const full = new ImagesDisplay([img.src]); full.show(); }; @@ -403,4 +397,4 @@ guild as invitejson["guild"] & { info: { cdn: string } } return colordiv; } } -export{ Embed }; +export {Embed}; diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index b6e04e82..bff6a99d 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -1,75 +1,73 @@ -import{ Contextmenu }from"./contextmenu.js"; -import{ Guild }from"./guild.js"; -import { emojijson } from "./jsontypes.js"; -import{ Localuser }from"./localuser.js"; -import { BinRead } from "./utils/binaryUtils.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Guild} from "./guild.js"; +import {emojijson} from "./jsontypes.js"; +import {Localuser} from "./localuser.js"; +import {BinRead} from "./utils/binaryUtils.js"; //I need to recompile the emoji format for translation -class Emoji{ +class Emoji { static emojis: { name: string; emojis: { - name: string; - emoji: string; + name: string; + emoji: string; }[]; }[]; name: string; id?: string; - emoji?:string; + emoji?: string; animated: boolean; owner: Guild | Localuser; - get guild(){ - if(this.owner instanceof Guild){ + get guild() { + if (this.owner instanceof Guild) { return this.owner; } return null; } - get localuser(){ - if(this.owner instanceof Guild){ + get localuser() { + if (this.owner instanceof Guild) { return this.owner.localuser; - }else{ + } else { return this.owner; } } - get info(){ + get info() { return this.owner.info; } - constructor( - json: emojijson, - owner: Guild | Localuser - ){ + constructor(json: emojijson, owner: Guild | Localuser) { this.name = json.name; this.id = json.id; - this.animated = json.animated||false; + this.animated = json.animated || false; this.owner = owner; - this.emoji=json.emoji; + this.emoji = json.emoji; } - getHTML(bigemoji: boolean = false){ - if(this.id){ + getHTML(bigemoji: boolean = false) { + if (this.id) { const emojiElem = document.createElement("img"); emojiElem.classList.add("md-emoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.crossOrigin = "anonymous"; - emojiElem.src =this.info.cdn+"/emojis/"+this.id+"."+(this.animated ? "gif" : "png")+"?size=32"; + emojiElem.src = + this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32"; emojiElem.alt = this.name; emojiElem.loading = "lazy"; return emojiElem; - }else if(this.emoji){ + } else if (this.emoji) { const emojiElem = document.createElement("span"); emojiElem.classList.add("md-emoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); - emojiElem.textContent=this.emoji; + emojiElem.textContent = this.emoji; return emojiElem; - }else{ + } else { throw new Error("This path should *never* be gone down, this means a malformed emoji"); } } - static decodeEmojiList(buffer: ArrayBuffer){ - const reader=new BinRead(buffer) - const build: { name: string; emojis: { name: string; emoji: string }[] }[] = []; + static decodeEmojiList(buffer: ArrayBuffer) { + const reader = new BinRead(buffer); + const build: {name: string; emojis: {name: string; emoji: string}[]}[] = []; let cats = reader.read16(); - for(; cats !== 0; cats--){ + for (; cats !== 0; cats--) { const name = reader.readString16(); const emojis: { name: string; @@ -77,7 +75,7 @@ class Emoji{ emoji: string; }[] = []; let emojinumber = reader.read16(); - for(; emojinumber !== 0; emojinumber--){ + for (; emojinumber !== 0; emojinumber--) { //console.log(emojis); const name = reader.readString8(); const len = reader.read8(); @@ -96,22 +94,18 @@ class Emoji{ } this.emojis = build; } - static grabEmoji(){ + static grabEmoji() { fetch("/emoji.bin") - .then(e=>{ + .then((e) => { return e.arrayBuffer(); }) - .then(e=>{ + .then((e) => { Emoji.decodeEmojiList(e); }); } - static async emojiPicker( - x: number, - y: number, - localuser: Localuser - ): Promise{ + static async emojiPicker(x: number, y: number, localuser: Localuser): Promise { let res: (r: Emoji | string) => void; - const promise: Promise = new Promise(r=>{ + const promise: Promise = new Promise((r) => { res = r; }); const menu = document.createElement("div"); @@ -130,33 +124,39 @@ class Emoji{ let isFirst = true; localuser.guilds - .filter(guild=>guild.id != "@me" && guild.emojis.length > 0) - .forEach(guild=>{ + .filter((guild) => guild.id != "@me" && guild.emojis.length > 0) + .forEach((guild) => { const select = document.createElement("div"); select.classList.add("emojiSelect"); - if(guild.properties.icon){ + if (guild.properties.icon) { const img = document.createElement("img"); img.classList.add("pfp", "servericon", "emoji-server"); img.crossOrigin = "anonymous"; - img.src = localuser.info.cdn+"/icons/"+guild.properties.id+"/"+guild.properties.icon+".png?size=48"; + img.src = + localuser.info.cdn + + "/icons/" + + guild.properties.id + + "/" + + guild.properties.icon + + ".png?size=48"; img.alt = "Server: " + guild.properties.name; select.appendChild(img); - }else{ + } else { const div = document.createElement("span"); div.textContent = guild.properties.name .replace(/'s /g, " ") - .replace(/\w+/g, word=>word[0]) + .replace(/\w+/g, (word) => word[0]) .replace(/\s/g, ""); select.append(div); } selection.append(select); - const clickEvent = ()=>{ + const clickEvent = () => { title.textContent = guild.properties.name; body.innerHTML = ""; - for(const emojit of guild.emojis){ + for (const emojit of guild.emojis) { const emojiElem = document.createElement("div"); emojiElem.classList.add("emojiSelect"); @@ -166,14 +166,14 @@ class Emoji{ name: emojit.name, animated: emojit.animated as boolean, }, - localuser + localuser, ); emojiElem.append(emojiClass.getHTML()); body.append(emojiElem); - emojiElem.addEventListener("click", ()=>{ + emojiElem.addEventListener("click", () => { res(emojiClass); - if(Contextmenu.currentmenu !== ""){ + if (Contextmenu.currentmenu !== "") { Contextmenu.currentmenu.remove(); } }); @@ -181,14 +181,14 @@ class Emoji{ }; select.addEventListener("click", clickEvent); - if(isFirst){ + if (isFirst) { clickEvent(); isFirst = false; } }); - setTimeout(()=>{ - if(Contextmenu.currentmenu != ""){ + setTimeout(() => { + if (Contextmenu.currentmenu != "") { Contextmenu.currentmenu.remove(); } document.body.append(menu); @@ -197,29 +197,29 @@ class Emoji{ }, 10); let i = 0; - for(const thing of Emoji.emojis){ + for (const thing of Emoji.emojis) { const select = document.createElement("div"); select.textContent = thing.emojis[0].emoji; select.classList.add("emojiSelect"); selection.append(select); - const clickEvent = ()=>{ + const clickEvent = () => { title.textContent = thing.name; body.innerHTML = ""; - for(const emojit of thing.emojis){ + for (const emojit of thing.emojis) { const emoji = document.createElement("div"); emoji.classList.add("emojiSelect"); emoji.textContent = emojit.emoji; body.append(emoji); - emoji.onclick = _=>{ + emoji.onclick = (_) => { res(emojit.emoji); - if(Contextmenu.currentmenu !== ""){ + if (Contextmenu.currentmenu !== "") { Contextmenu.currentmenu.remove(); } }; } }; select.onclick = clickEvent; - if(i === 0){ + if (i === 0) { clickEvent(); } i++; @@ -228,40 +228,39 @@ class Emoji{ menu.append(body); return promise; } - static searchEmoji(search:string,localuser:Localuser,results=50):[Emoji,number][]{ - const ranked:[emojijson,number][]=[]; - function similar(json:emojijson){ - if(json.name.includes(search)){ - ranked.push([json,search.length/json.name.length]); + static searchEmoji(search: string, localuser: Localuser, results = 50): [Emoji, number][] { + const ranked: [emojijson, number][] = []; + function similar(json: emojijson) { + if (json.name.includes(search)) { + ranked.push([json, search.length / json.name.length]); return true; - }else if(json.name.toLowerCase().includes(search.toLowerCase())){ - ranked.push([json,search.length/json.name.length/1.4]); + } else if (json.name.toLowerCase().includes(search.toLowerCase())) { + ranked.push([json, search.length / json.name.length / 1.4]); return true; - }else{ + } else { return false; } } - for(const group of this.emojis){ - for(const emoji of group.emojis){ - similar(emoji) + for (const group of this.emojis) { + for (const emoji of group.emojis) { + similar(emoji); } } - const weakGuild=new WeakMap(); - for(const guild of localuser.guilds){ - if(guild.id!=="@me"&&guild.emojis.length!==0){ - for(const emoji of guild.emojis){ - if(similar(emoji)){ - weakGuild.set(emoji,guild); - }; + const weakGuild = new WeakMap(); + for (const guild of localuser.guilds) { + if (guild.id !== "@me" && guild.emojis.length !== 0) { + for (const emoji of guild.emojis) { + if (similar(emoji)) { + weakGuild.set(emoji, guild); + } } } } - ranked.sort((a,b)=>b[1]-a[1]); - return ranked.splice(0,results).map(a=>{ - return [new Emoji(a[0],weakGuild.get(a[0])||localuser),a[1]]; - - }) + ranked.sort((a, b) => b[1] - a[1]); + return ranked.splice(0, results).map((a) => { + return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]]; + }); } } Emoji.grabEmoji(); -export{ Emoji }; +export {Emoji}; diff --git a/src/webpage/file.ts b/src/webpage/file.ts index a16dcb57..f394ad48 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -1,8 +1,8 @@ -import{ Message }from"./message.js"; -import{ filejson }from"./jsontypes.js"; -import { ImagesDisplay } from "./disimg.js"; +import {Message} from "./message.js"; +import {filejson} from "./jsontypes.js"; +import {ImagesDisplay} from "./disimg.js"; -class File{ +class File { owner: Message | null; id: string; filename: string; @@ -12,7 +12,7 @@ class File{ proxy_url: string | undefined; url: string; size: number; - constructor(fileJSON: filejson, owner: Message | null){ + constructor(fileJSON: filejson, owner: Message | null) { this.owner = owner; this.id = fileJSON.id; this.filename = fileJSON.filename; @@ -24,9 +24,9 @@ class File{ this.content_type = fileJSON.content_type; this.size = fileJSON.size; } - getHTML(temp: boolean = false): HTMLElement{ + getHTML(temp: boolean = false): HTMLElement { const src = this.proxy_url || this.url; - if(this.width && this.height){ + if (this.width && this.height) { let scale = 1; const max = 96 * 3; scale = Math.max(scale, this.width / max); @@ -34,35 +34,35 @@ class File{ this.width /= scale; this.height /= scale; } - if(this.content_type.startsWith("image/")){ + if (this.content_type.startsWith("image/")) { const div = document.createElement("div"); const img = document.createElement("img"); img.classList.add("messageimg"); div.classList.add("messageimgdiv"); - img.onclick = function(){ + img.onclick = function () { const full = new ImagesDisplay([img.src]); full.show(); }; img.src = src; div.append(img); - if(this.width){ + if (this.width) { div.style.width = this.width + "px"; div.style.height = this.height + "px"; } return div; - }else if(this.content_type.startsWith("video/")){ + } else if (this.content_type.startsWith("video/")) { const video = document.createElement("video"); const source = document.createElement("source"); source.src = src; video.append(source); source.type = this.content_type; video.controls = !temp; - if(this.width && this.height){ + if (this.width && this.height) { video.width = this.width; video.height = this.height; } return video; - }else if(this.content_type.startsWith("audio/")){ + } else if (this.content_type.startsWith("audio/")) { const audio = document.createElement("audio"); const source = document.createElement("source"); source.src = src; @@ -70,11 +70,11 @@ class File{ source.type = this.content_type; audio.controls = !temp; return audio; - }else{ + } else { return this.createunknown(); } } - upHTML(files: Blob[], file: globalThis.File): HTMLElement{ + upHTML(files: Blob[], file: globalThis.File): HTMLElement { const div = document.createElement("div"); const contained = this.getHTML(true); div.classList.add("containedFile"); @@ -82,9 +82,9 @@ class File{ const controls = document.createElement("div"); const garbage = document.createElement("button"); const icon = document.createElement("span"); - icon.classList.add("svgicon","svg-delete"); + icon.classList.add("svgicon", "svg-delete"); garbage.append(icon); - garbage.onclick = _=>{ + garbage.onclick = (_) => { div.remove(); files.splice(files.indexOf(file), 1); }; @@ -93,7 +93,7 @@ class File{ controls.append(garbage); return div; } - static initFromBlob(file: globalThis.File){ + static initFromBlob(file: globalThis.File) { return new File( { filename: file.name, @@ -105,10 +105,10 @@ class File{ url: URL.createObjectURL(file), proxy_url: undefined, }, - null + null, ); } - createunknown(): HTMLElement{ + createunknown(): HTMLElement { console.log("🗎"); const src = this.proxy_url || this.url; const div = document.createElement("table"); @@ -121,12 +121,12 @@ class File{ fileicon.classList.add("fileicon"); fileicon.rowSpan = 2; const nametd = document.createElement("td"); - if(src){ + if (src) { const a = document.createElement("a"); a.href = src; a.textContent = this.filename; nametd.append(a); - }else{ + } else { nametd.textContent = this.filename; } @@ -140,11 +140,13 @@ class File{ div.appendChild(sizetr); return div; } - static filesizehuman(fsize: number){ + static filesizehuman(fsize: number) { const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); - return( - Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong + return ( + Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + + " " + + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong ); } } -export{ File }; +export {File}; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index c217cb7a..1cd544fa 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -1,17 +1,24 @@ -import{ Channel }from"./channel.js"; -import{ Localuser }from"./localuser.js"; -import{ Contextmenu }from"./contextmenu.js"; -import{ Role, RoleList }from"./role.js"; -import{ Member }from"./member.js"; -import{ Dialog, Options, Settings }from"./settings.js"; -import{ Permissions }from"./permissions.js"; -import{ SnowFlake }from"./snowflake.js"; -import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson, emojipjson,}from"./jsontypes.js"; -import{ User }from"./user.js"; -import { I18n } from "./i18n.js"; -import { Emoji } from "./emoji.js"; - -class Guild extends SnowFlake{ +import {Channel} from "./channel.js"; +import {Localuser} from "./localuser.js"; +import {Contextmenu} from "./contextmenu.js"; +import {Role, RoleList} from "./role.js"; +import {Member} from "./member.js"; +import {Dialog, Options, Settings} from "./settings.js"; +import {Permissions} from "./permissions.js"; +import {SnowFlake} from "./snowflake.js"; +import { + channeljson, + guildjson, + memberjson, + invitejson, + rolesjson, + emojipjson, +} from "./jsontypes.js"; +import {User} from "./user.js"; +import {I18n} from "./i18n.js"; +import {Emoji} from "./emoji.js"; + +class Guild extends SnowFlake { owner!: Localuser; headers!: Localuser["headers"]; channels!: Channel[]; @@ -29,68 +36,82 @@ class Guild extends SnowFlake{ html!: HTMLElement; emojis!: emojipjson[]; large!: boolean; - members=new Set(); + members = new Set(); static contextmenu = new Contextmenu("guild menu"); - static setupcontextmenu(){ - Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.copyId"), function(this: Guild){ - navigator.clipboard.writeText(this.id); - }); + static setupcontextmenu() { + Guild.contextmenu.addbutton( + () => I18n.getTranslation("guild.copyId"), + function (this: Guild) { + navigator.clipboard.writeText(this.id); + }, + ); - Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.markRead"), function(this: Guild){ - this.markAsRead(); - }); + Guild.contextmenu.addbutton( + () => I18n.getTranslation("guild.markRead"), + function (this: Guild) { + this.markAsRead(); + }, + ); - Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.notifications"), function(this: Guild){ - this.setnotifcation(); - }); + Guild.contextmenu.addbutton( + () => I18n.getTranslation("guild.notifications"), + function (this: Guild) { + this.setnotifcation(); + }, + ); this.contextmenu.addbutton( - ()=>I18n.getTranslation("user.editServerProfile"), - function(){ + () => I18n.getTranslation("user.editServerProfile"), + function () { this.member.showEditProfile(); - } + }, ); Guild.contextmenu.addbutton( - ()=>I18n.getTranslation("guild.leave"), - function(this: Guild){ + () => I18n.getTranslation("guild.leave"), + function (this: Guild) { this.confirmleave(); }, null, - function(_){ + function (_) { return this.properties.owner_id !== this.member.user.id; - } + }, ); Guild.contextmenu.addbutton( - ()=>I18n.getTranslation("guild.delete"), - function(this: Guild){ + () => I18n.getTranslation("guild.delete"), + function (this: Guild) { this.confirmDelete(); }, null, - function(_){ + function (_) { return this.properties.owner_id === this.member.user.id; - } + }, ); Guild.contextmenu.addbutton( - ()=>I18n.getTranslation("guild.makeInvite"), - function(this: Guild){ - const d=new Dialog(""); + () => I18n.getTranslation("guild.makeInvite"), + function (this: Guild) { + const d = new Dialog(""); this.makeInviteMenu(d.options); d.show(); }, null, - _=>true, - function(){ + (_) => true, + function () { return this.member.hasPermission("CREATE_INSTANT_INVITE"); - } + }, + ); + Guild.contextmenu.addbutton( + () => I18n.getTranslation("guild.settings"), + function (this: Guild) { + this.generateSettings(); + }, + null, + function () { + return this.member.hasPermission("MANAGE_GUILD"); + }, ); - Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.settings"), function(this: Guild){ - this.generateSettings(); - },null,function(){ - return this.member.hasPermission("MANAGE_GUILD"); - }); /* -----things left for later----- guild.contextmenu.addbutton("Leave Guild",function(){ console.log(this) @@ -102,162 +123,178 @@ class Guild extends SnowFlake{ },null,_=>{return thisuser.isAdmin()}) */ } - generateSettings(){ - const settings = new Settings(I18n.getTranslation("guild.settingsFor",this.properties.name)); - const textChannels=this.channels.filter(e=>{ + generateSettings() { + const settings = new Settings(I18n.getTranslation("guild.settingsFor", this.properties.name)); + const textChannels = this.channels.filter((e) => { //TODO there are almost certainly more types. is Voice valid? - return new Set([0,5]).has(e.type); + return new Set([0, 5]).has(e.type); }); { const overview = settings.addButton(I18n.getTranslation("guild.overview")); - const form = overview.addForm("", _=>{}, { + const form = overview.addForm("", (_) => {}, { headers: this.headers, traditionalSubmit: true, fetchURL: this.info.api + "/guilds/" + this.id, method: "PATCH", }); - form.addTextInput(I18n.getTranslation("guild.name:"), "name", { initText: this.properties.name }); + form.addTextInput(I18n.getTranslation("guild.name:"), "name", { + initText: this.properties.name, + }); form.addMDInput(I18n.getTranslation("guild.description:"), "description", { initText: this.properties.description, }); - form.addFileInput(I18n.getTranslation("guild.banner:"), "banner", { clear: true }); - form.addFileInput(I18n.getTranslation("guild.icon:"), "icon", { clear: true }); + form.addFileInput(I18n.getTranslation("guild.banner:"), "banner", {clear: true}); + form.addFileInput(I18n.getTranslation("guild.icon:"), "icon", {clear: true}); form.addHR(); - const sysmap=[null,...textChannels.map(e=>e.id)]; - form.addSelect(I18n.getTranslation("guild.systemSelect:"), "system_channel_id", - ["No system messages",...textChannels.map(e=>e.name)],{defaultIndex:sysmap.indexOf(this.properties.system_channel_id)} - ,sysmap); + const sysmap = [null, ...textChannels.map((e) => e.id)]; + form.addSelect( + I18n.getTranslation("guild.systemSelect:"), + "system_channel_id", + ["No system messages", ...textChannels.map((e) => e.name)], + {defaultIndex: sysmap.indexOf(this.properties.system_channel_id)}, + sysmap, + ); - form.addCheckboxInput(I18n.getTranslation("guild.sendrandomwelcome?"),"s1",{ - initState:!(this.properties.system_channel_flags&1) + form.addCheckboxInput(I18n.getTranslation("guild.sendrandomwelcome?"), "s1", { + initState: !(this.properties.system_channel_flags & 1), }); - form.addCheckboxInput(I18n.getTranslation("guild.stickWelcomeReact?"),"s4",{ - initState:!(this.properties.system_channel_flags&8) + form.addCheckboxInput(I18n.getTranslation("guild.stickWelcomeReact?"), "s4", { + initState: !(this.properties.system_channel_flags & 8), }); - form.addCheckboxInput(I18n.getTranslation("guild.boostMessage?"),"s2",{ - initState:!(this.properties.system_channel_flags&2) + form.addCheckboxInput(I18n.getTranslation("guild.boostMessage?"), "s2", { + initState: !(this.properties.system_channel_flags & 2), }); - form.addCheckboxInput(I18n.getTranslation("guild.helpTips?"),"s3",{ - initState:!(this.properties.system_channel_flags&4) + form.addCheckboxInput(I18n.getTranslation("guild.helpTips?"), "s3", { + initState: !(this.properties.system_channel_flags & 4), }); - form.addPreprocessor((e:any)=>{ - let bits=0; - bits+=(1-e.s1)*1; + form.addPreprocessor((e: any) => { + let bits = 0; + bits += (1 - e.s1) * 1; delete e.s1; - bits+=(1-e.s2)*2; + bits += (1 - e.s2) * 2; delete e.s2; - bits+=(1-e.s3)*4; + bits += (1 - e.s3) * 4; delete e.s3; - bits+= (1-e.s4)*8; + bits += (1 - e.s4) * 8; delete e.s4; - e.system_channel_flags=bits; - }) + e.system_channel_flags = bits; + }); form.addHR(); - form.addSelect(I18n.getTranslation("guild.defaultNoti"),"default_message_notifications", - [I18n.getTranslation("guild.onlyMentions"),I18n.getTranslation("guild.all")], - { - defaultIndex:[1,0].indexOf(this.properties.default_message_notifications), - radio:true - },[1,0]); + form.addSelect( + I18n.getTranslation("guild.defaultNoti"), + "default_message_notifications", + [I18n.getTranslation("guild.onlyMentions"), I18n.getTranslation("guild.all")], + { + defaultIndex: [1, 0].indexOf(this.properties.default_message_notifications), + radio: true, + }, + [1, 0], + ); form.addHR(); let region = this.properties.region; - if(!region){ + if (!region) { region = ""; } - form.addTextInput(I18n.getTranslation("guild.region:"), "region", { initText: region }); + form.addTextInput(I18n.getTranslation("guild.region:"), "region", {initText: region}); } - this.makeInviteMenu(settings.addButton(I18n.getTranslation("invite.inviteMaker")),textChannels); + this.makeInviteMenu( + settings.addButton(I18n.getTranslation("invite.inviteMaker")), + textChannels, + ); const s1 = settings.addButton(I18n.getTranslation("guild.roles")); const permlist: [Role, Permissions][] = []; - for(const thing of this.roles){ + for (const thing of this.roles) { permlist.push([thing, thing.permissions]); } - s1.options.push( - new RoleList(permlist, this, this.updateRolePermissions.bind(this),false) - ); + s1.options.push(new RoleList(permlist, this, this.updateRolePermissions.bind(this), false)); { - const emoji=settings.addButton("Emojis"); - emoji.addButtonInput("","Upload Emoji",()=>{ - const popup=new Dialog("Upload emoji"); - const form=popup.options.addForm("",()=>{ - popup.hide(); - },{ - fetchURL:`${this.info.api}/guilds/${this.id}/emojis`, - method:"POST", - headers:this.headers - }); - form.addFileInput("Image:","image",{required:true}); - form.addTextInput("Name:","name",{required:true}); + const emoji = settings.addButton("Emojis"); + emoji.addButtonInput("", "Upload Emoji", () => { + const popup = new Dialog("Upload emoji"); + const form = popup.options.addForm( + "", + () => { + popup.hide(); + }, + { + fetchURL: `${this.info.api}/guilds/${this.id}/emojis`, + method: "POST", + headers: this.headers, + }, + ); + form.addFileInput("Image:", "image", {required: true}); + form.addTextInput("Name:", "name", {required: true}); popup.show(); }); - const containdiv=document.createElement("div"); - const genDiv=()=>{ - containdiv.innerHTML=""; - for(const emoji of this.emojis){ - const div=document.createElement("div"); - div.classList.add("flexltr","emojiOption"); - const emojic=new Emoji(emoji,this); - - const text=document.createElement("input"); - text.type="text"; - text.value=emoji.name; - text.addEventListener("change",()=>{ - fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`,{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({name:text.value}) - }).then(e=>{if(!e.ok)text.value=emoji.name;})//if not ok, undo + const containdiv = document.createElement("div"); + const genDiv = () => { + containdiv.innerHTML = ""; + for (const emoji of this.emojis) { + const div = document.createElement("div"); + div.classList.add("flexltr", "emojiOption"); + const emojic = new Emoji(emoji, this); + + const text = document.createElement("input"); + text.type = "text"; + text.value = emoji.name; + text.addEventListener("change", () => { + fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({name: text.value}), + }).then((e) => { + if (!e.ok) text.value = emoji.name; + }); //if not ok, undo }); - const del=document.createElement("span"); - del.classList.add("svgicon", "svg-x","deleteEmoji"); - del.onclick=()=>{ - const diaolog=new Dialog(""); + const del = document.createElement("span"); + del.classList.add("svgicon", "svg-x", "deleteEmoji"); + del.onclick = () => { + const diaolog = new Dialog(""); diaolog.options.addTitle("Are you sure you want to delete this emoji?"); - const options=diaolog.options.addOptions("",{ltr:true}); - options.addButtonInput("",I18n.getTranslation("yes"),()=>{ - fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`,{ - method:"DELETE", - headers:this.headers - }) + const options = diaolog.options.addOptions("", {ltr: true}); + options.addButtonInput("", I18n.getTranslation("yes"), () => { + fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, { + method: "DELETE", + headers: this.headers, + }); diaolog.hide(); }); - options.addButtonInput("",I18n.getTranslation("no"),()=>{ + options.addButtonInput("", I18n.getTranslation("no"), () => { diaolog.hide(); - }) + }); diaolog.show(); - } - + }; - div.append(emojic.getHTML(true),":",text,":",del); + div.append(emojic.getHTML(true), ":", text, ":", del); containdiv.append(div); } - } - this.onEmojiUpdate=()=>{ - if(!document.body.contains(containdiv)){ - this.onEmojiUpdate=()=>{}; + }; + this.onEmojiUpdate = () => { + if (!document.body.contains(containdiv)) { + this.onEmojiUpdate = () => {}; return; } genDiv(); - } + }; genDiv(); emoji.addHTMLArea(containdiv); } settings.show(); } - makeInviteMenu(options:Options,valid:void|(Channel[])){ - if(!valid){ - valid=this.channels.filter(e=>{ + makeInviteMenu(options: Options, valid: void | Channel[]) { + if (!valid) { + valid = this.channels.filter((e) => { //TODO there are almost certainly more types. is Voice valid? - return new Set([0,5]).has(e.type); + return new Set([0, 5]).has(e.type); }); } - let channel=valid[0]; + let channel = valid[0]; const div = document.createElement("div"); div.classList.add("invitediv"); const text = document.createElement("span"); @@ -270,13 +307,13 @@ class Guild extends SnowFlake{ const copy = document.createElement("span"); copy.classList.add("copybutton", "svgicon", "svg-copy"); copycontainer.append(copy); - copycontainer.onclick = _=>{ - if(text.textContent){ + copycontainer.onclick = (_) => { + if (text.textContent) { navigator.clipboard.writeText(text.textContent); } }; div.append(copycontainer); - const update = ()=>{ + const update = () => { fetch(`${this.info.api}/channels/${channel.id}/invites`, { method: "POST", headers: this.headers, @@ -286,11 +323,11 @@ class Guild extends SnowFlake{ target_user_id: null, max_age: expires + "", max_uses: uses, - temporary: uses !== 0 + temporary: uses !== 0, }), }) - .then(_=>_.json()) - .then(json=>{ + .then((_) => _.json()) + .then((json) => { const params = new URLSearchParams(""); params.set("instance", this.info.wellknown); const encoded = params.toString(); @@ -299,91 +336,102 @@ class Guild extends SnowFlake{ }; options.addTitle(I18n.getTranslation("inviteOptions.title")); - const text2=options.addText(""); - options.addSelect(I18n.getTranslation("invite.channel:"),()=>{},valid.map(e=>e.name)) - .watchForChange((e)=>{ - channel=valid[e]; - text2.setText(I18n.getTranslation("invite.subtext",channel.name,this.properties.name)); - }) - + const text2 = options.addText(""); + options + .addSelect( + I18n.getTranslation("invite.channel:"), + () => {}, + valid.map((e) => e.name), + ) + .watchForChange((e) => { + channel = valid[e]; + text2.setText(I18n.getTranslation("invite.subtext", channel.name, this.properties.name)); + }); - options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, - ["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) - ).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];}; + options.addSelect( + I18n.getTranslation("invite.expireAfter"), + () => {}, + ["30m", "1h", "6h", "12h", "1d", "7d", "30d", "never"].map((e) => + I18n.getTranslation("inviteOptions." + e), + ), + ).onchange = (e) => { + expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e]; + }; - const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) - timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) - options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) - .onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];}; + const timeOptions = ["1", "5", "10", "25", "50", "100"].map((e) => + I18n.getTranslation("inviteOptions.limit", e), + ); + timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")); + options.addSelect(I18n.getTranslation("invite.expireAfter"), () => {}, timeOptions).onchange = ( + e, + ) => { + uses = [0, 1, 5, 10, 25, 50, 100][e]; + }; - options.addButtonInput("",I18n.getTranslation("invite.createInvite"),()=>{ + options.addButtonInput("", I18n.getTranslation("invite.createInvite"), () => { update(); - }) + }); options.addHTMLArea(div); } - roleUpdate:(role:Role,added:-1|0|1)=>unknown=()=>{}; - sortRoles(){ - this.roles.sort((a,b)=>(b.position-a.position)); + 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(_=>{ + 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); + 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); + this.roleUpdate(role, 1); } - updateRole(rolej:rolesjson){ - const role=this.roleids.get(rolej.id) as Role; + updateRole(rolej: rolesjson) { + const role = this.roleids.get(rolej.id) as Role; role.newJson(rolej); - this.roleUpdate(role,0); + 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; + 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; + if (!member) return; member.update(json); - if(member===this.member){ + if (member === this.member) { console.log(member); this.loadGuild(); } } - deleteRole(id:string){ + deleteRole(id: string) { const role = this.roleids.get(id); - if(!role) return; + if (!role) return; this.roleids.delete(id); - this.roles.splice(this.roles.indexOf(role),1); - this.roleUpdate(role,-1); - } - onEmojiUpdate=(_:emojipjson[])=>{}; - constructor( - json: guildjson | -1, - owner: Localuser, - member: memberjson | User | null - ){ - if(json === -1 || member === null){ + this.roles.splice(this.roles.indexOf(role), 1); + this.roleUpdate(role, -1); + } + onEmojiUpdate = (_: emojipjson[]) => {}; + constructor(json: guildjson | -1, owner: Localuser, member: memberjson | User | null) { + if (json === -1 || member === null) { super("@me"); return; } - if(json.stickers.length){ + if (json.stickers.length) { console.log(json.stickers, ":3"); } super(json.id); @@ -398,52 +446,57 @@ class Guild extends SnowFlake{ this.roleids = new Map(); this.message_notifications = 0; - for(const roley of json.roles){ + for (const roley of json.roles) { const roleh = new Role(roley, this); this.roles.push(roleh); this.roleids.set(roleh.id, roleh); } this.sortRoles(); - if(member instanceof User){ + if (member instanceof User) { console.warn(member); - Member.resolveMember(member, this).then(_=>{ - if(_){ + Member.resolveMember(member, this).then((_) => { + if (_) { this.member = _; - }else{ + } else { console.error("Member was unable to resolve"); } }); - }else{ - Member.new(member, this).then(_=>{ - if(_){ + } else { + Member.new(member, this).then((_) => { + if (_) { this.member = _; } }); } - this.perminfo ??= { channels: {} }; - for(const thing of json.channels){ + this.perminfo ??= {channels: {}}; + for (const thing of json.channels) { const temp = new Channel(thing, this); this.channels.push(temp); this.localuser.channelids.set(temp.id, temp); } this.headchannels = []; - for(const thing of this.channels){ + for (const thing of this.channels) { const parent = thing.resolveparent(this); - if(!parent){ + if (!parent) { this.headchannels.push(thing); } } this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel); } - get perminfo(){ + get perminfo() { return this.localuser.perminfo.guilds[this.id]; } - set perminfo(e){ + set perminfo(e) { this.localuser.perminfo.guilds[this.id] = e; } notisetting(settings: { - channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; + channel_overrides: { + message_notifications: number; + muted: boolean; + mute_config: {selected_time_window: number; end_time: number}; + channel_id: string; + }[]; message_notifications: any; flags?: number; hide_muted_channels?: boolean; @@ -456,103 +509,112 @@ class Guild extends SnowFlake{ suppress_roles?: boolean; version?: number; guild_id?: string; - }){ + }) { this.message_notifications = settings.message_notifications; - for(const override of settings.channel_overrides){ - const channel=this.localuser.channelids.get(override.channel_id); - if(!channel) continue; + for (const override of settings.channel_overrides) { + const channel = this.localuser.channelids.get(override.channel_id); + if (!channel) continue; channel.handleUserOverrides(override); } } - setnotifcation(){ - - const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e)); - const notiselect=new Dialog(""); - const form=notiselect.options.addForm("",(_,sent:any)=>{ - notiselect.hide(); - this.message_notifications = sent.message_notifications; - },{ - fetchURL:`${this.info.api}/users/@me/guilds/${this.id}/settings/`, - method:"PATCH", - headers:this.headers - }); - form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ - radio:true, - defaultIndex:this.message_notifications - },[0,1,2]); + setnotifcation() { + const options = ["all", "onlyMentions", "none"].map((e) => I18n.getTranslation("guild." + e)); + const notiselect = new Dialog(""); + const form = notiselect.options.addForm( + "", + (_, sent: any) => { + notiselect.hide(); + this.message_notifications = sent.message_notifications; + }, + { + fetchURL: `${this.info.api}/users/@me/guilds/${this.id}/settings/`, + method: "PATCH", + headers: this.headers, + }, + ); + form.addSelect( + I18n.getTranslation("guild.selectnoti"), + "message_notifications", + options, + { + radio: true, + defaultIndex: this.message_notifications, + }, + [0, 1, 2], + ); notiselect.show(); } - confirmleave(){ + confirmleave() { const full = new Dialog(""); - full.options.addTitle(I18n.getTranslation("guild.confirmLeave")) - const options=full.options.addOptions("",{ltr:true}); - options.addButtonInput("",I18n.getTranslation("guild.yesLeave"),()=>{ - this.leave().then(_=>{ + full.options.addTitle(I18n.getTranslation("guild.confirmLeave")); + const options = full.options.addOptions("", {ltr: true}); + options.addButtonInput("", I18n.getTranslation("guild.yesLeave"), () => { + this.leave().then((_) => { full.hide(); }); }); - options.addButtonInput("",I18n.getTranslation("guild.noLeave"),()=>{ + options.addButtonInput("", I18n.getTranslation("guild.noLeave"), () => { full.hide(); }); full.show(); } - async leave(){ + async leave() { return fetch(this.info.api + "/users/@me/guilds/" + this.id, { method: "DELETE", headers: this.headers, }); } - printServers(){ + printServers() { let build = ""; - for(const thing of this.headchannels){ + for (const thing of this.headchannels) { build += thing.name + ":" + thing.position + "\n"; - for(const thingy of thing.children){ + for (const thingy of thing.children) { build += " " + thingy.name + ":" + thingy.position + "\n"; } } console.log(build); } - calculateReorder(){ + calculateReorder() { let position = -1; const build: { id: string; position: number | undefined; parent_id: string | undefined; }[] = []; - for(const thing of this.headchannels){ + for (const thing of this.headchannels) { const thisthing: { - id: string; - position: number | undefined; - parent_id: string | undefined; - } = { id: thing.id, position: undefined, parent_id: undefined }; - if(thing.position <= position){ + id: string; + position: number | undefined; + parent_id: string | undefined; + } = {id: thing.id, position: undefined, parent_id: undefined}; + if (thing.position <= position) { thing.position = thisthing.position = position + 1; } position = thing.position; console.log(position); - if(thing.move_id && thing.move_id !== thing.parent_id){ + if (thing.move_id && thing.move_id !== thing.parent_id) { thing.parent_id = thing.move_id; thisthing.parent_id = thing.parent?.id; thing.move_id = undefined; } - if(thisthing.position || thisthing.parent_id){ + if (thisthing.position || thisthing.parent_id) { build.push(thisthing); } - if(thing.children.length > 0){ + if (thing.children.length > 0) { const things = thing.calculateReorder(); - for(const thing of things){ + for (const thing of things) { build.push(thing); } } } console.log(build); this.printServers(); - if(build.length === 0){ + if (build.length === 0) { return; } const serverbug = false; - if(serverbug){ - for(const thing of build){ + if (serverbug) { + for (const thing of build) { console.log(build, thing); fetch(this.info.api + "/guilds/" + this.id + "/channels", { method: "PATCH", @@ -560,7 +622,7 @@ class Guild extends SnowFlake{ body: JSON.stringify([thing]), }); } - }else{ + } else { fetch(this.info.api + "/guilds/" + this.id + "/channels", { method: "PATCH", headers: this.headers, @@ -568,174 +630,175 @@ class Guild extends SnowFlake{ }); } } - get localuser(){ + get localuser() { return this.owner; } - get info(){ + get info() { return this.owner.info; } - sortchannels(){ - this.headchannels.sort((a, b)=>{ + sortchannels() { + this.headchannels.sort((a, b) => { return a.position - b.position; }); } - static generateGuildIcon(guild: Guild | (invitejson["guild"] & { info: { cdn: string } })){ + static generateGuildIcon(guild: Guild | (invitejson["guild"] & {info: {cdn: string}})) { const divy = document.createElement("div"); divy.classList.add("servernoti"); const noti = document.createElement("div"); noti.classList.add("unread"); divy.append(noti); - if(guild instanceof Guild){ + if (guild instanceof Guild) { guild.localuser.guildhtml.set(guild.id, divy); - guild.html=divy; + guild.html = divy; } let icon: string | null; - if(guild instanceof Guild){ + if (guild instanceof Guild) { icon = guild.properties.icon; - }else{ + } else { icon = guild.icon; } - if(icon !== null){ + if (icon !== null) { const img = document.createElement("img"); img.classList.add("pfp", "servericon"); img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; divy.appendChild(img); - if(guild instanceof Guild){ - img.onclick = ()=>{ + if (guild instanceof Guild) { + img.onclick = () => { console.log(guild.loadGuild); guild.loadGuild(); guild.loadChannel(); }; - Guild.contextmenu.bindContextmenu(img, guild,undefined); + Guild.contextmenu.bindContextmenu(img, guild, undefined); } - }else{ + } else { const div = document.createElement("div"); let name: string; - if(guild instanceof Guild){ + if (guild instanceof Guild) { name = guild.properties.name; - }else{ + } else { name = guild.name; } const build = name .replace(/'s /g, " ") - .replace(/\w+/g, word=>word[0]) + .replace(/\w+/g, (word) => word[0]) .replace(/\s/g, ""); div.textContent = build; div.classList.add("blankserver", "servericon"); divy.appendChild(div); - if(guild instanceof Guild){ - div.onclick = ()=>{ + if (guild instanceof Guild) { + div.onclick = () => { guild.loadGuild(); guild.loadChannel(); }; - Guild.contextmenu.bindContextmenu(div, guild,undefined); + Guild.contextmenu.bindContextmenu(div, guild, undefined); } } return divy; } - generateGuildIcon(){ + generateGuildIcon() { return Guild.generateGuildIcon(this); } - confirmDelete(){ + confirmDelete() { let confirmname = ""; const full = new Dialog(""); - full.options.addTitle(I18n.getTranslation("guild.confirmDelete",this.properties.name)); - full.options.addTextInput(I18n.getTranslation("guild.serverName"),()=>{}).onchange=(e)=>confirmname=e; + full.options.addTitle(I18n.getTranslation("guild.confirmDelete", this.properties.name)); + full.options.addTextInput(I18n.getTranslation("guild.serverName"), () => {}).onchange = (e) => + (confirmname = e); - const options=full.options.addOptions("",{ltr:true}); - options.addButtonInput("",I18n.getTranslation("guild.yesDelete"),()=>{ - if(confirmname !== this.properties.name){ + const options = full.options.addOptions("", {ltr: true}); + options.addButtonInput("", I18n.getTranslation("guild.yesDelete"), () => { + if (confirmname !== this.properties.name) { //TODO maybe some sort of form error? idk alert("names don't match"); return; } - this.delete().then(_=>{ + this.delete().then((_) => { full.hide(); }); }); - options.addButtonInput("",I18n.getTranslation("guild.noDelete"),()=>{ + options.addButtonInput("", I18n.getTranslation("guild.noDelete"), () => { full.hide(); }); full.show(); } - async delete(){ + async delete() { return fetch(this.info.api + "/guilds/" + this.id + "/delete", { method: "POST", headers: this.headers, }); } - get mentions(){ - let mentions=0; - for(const thing of this.channels){ - mentions+=thing.mentions; + get mentions() { + let mentions = 0; + for (const thing of this.channels) { + mentions += thing.mentions; } return mentions; } - unreads(html?: HTMLElement | undefined){ - if(html){ + unreads(html?: HTMLElement | undefined) { + if (html) { this.html = html; - }else{ + } else { html = this.html; } - if(!html){ + if (!html) { return; } let read = true; - let mentions=this.mentions; - for(const thing of this.channels){ - if(thing.hasunreads){ + let mentions = this.mentions; + for (const thing of this.channels) { + if (thing.hasunreads) { read = false; break; } } - const noti=html.children[0]; - if(mentions!==0){ + const noti = html.children[0]; + if (mentions !== 0) { noti.classList.add("pinged"); - noti.textContent=""+mentions; - }else{ - noti.textContent=""; + noti.textContent = "" + mentions; + } else { + noti.textContent = ""; noti.classList.remove("pinged"); } - if(read){ + if (read) { noti.classList.remove("notiunread"); - }else{ + } else { noti.classList.add("notiunread"); } } - getHTML(){ + getHTML() { //this.printServers(); this.sortchannels(); this.printServers(); const build = document.createElement("div"); - for(const thing of this.headchannels){ + for (const thing of this.headchannels) { build.appendChild(thing.createguildHTML(this.isAdmin())); } return build; } - isAdmin(){ + isAdmin() { return this.member.isAdmin(); } - async markAsRead(){ + async markAsRead() { const build: { - read_states: { - channel_id: string; - message_id: string | null | undefined; - read_state_type: number; - }[]; - } = { read_states: [] }; - for(const thing of this.channels){ - if(thing.hasunreads){ + read_states: { + channel_id: string; + message_id: string | null | undefined; + read_state_type: number; + }[]; + } = {read_states: []}; + for (const thing of this.channels) { + if (thing.hasunreads) { build.read_states.push({ channel_id: thing.id, message_id: thing.lastmessageid, read_state_type: 0, }); thing.lastreadmessageid = thing.lastmessageid; - if(!thing.myhtml)continue; + if (!thing.myhtml) continue; thing.myhtml.classList.remove("cunread"); } } @@ -746,29 +809,29 @@ class Guild extends SnowFlake{ body: JSON.stringify(build), }); } - hasRole(r: Role | string){ + hasRole(r: Role | string) { console.log("this should run"); - if(r instanceof Role){ + if (r instanceof Role) { r = r.id; } return this.member.hasRole(r); } - loadChannel(ID?: string | undefined| null,addstate=true){ - if(ID){ + loadChannel(ID?: string | undefined | null, addstate = true) { + if (ID) { const channel = this.localuser.channelids.get(ID); - if(channel){ + if (channel) { channel.getHTML(addstate); return; } } - if(this.prevchannel&&ID!==null){ + if (this.prevchannel && ID !== null) { console.log(this.prevchannel); this.prevchannel.getHTML(addstate); return; } - if(this.id!=="@me"){ - for(const thing of this.channels){ - if(thing.type!==4){ + if (this.id !== "@me") { + for (const thing of this.channels) { + if (thing.type !== 4) { thing.getHTML(addstate); return; } @@ -777,14 +840,14 @@ class Guild extends SnowFlake{ this.removePrevChannel(); this.noChannel(addstate); } - removePrevChannel(){ - if(this.localuser.channelfocus){ + removePrevChannel() { + if (this.localuser.channelfocus) { this.localuser.channelfocus.infinite.delete(); } - if(this !== this.localuser.lookingguild){ + if (this !== this.localuser.lookingguild) { this.loadGuild(); } - if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ + if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); } this.prevchannel = undefined; @@ -793,14 +856,14 @@ class Guild extends SnowFlake{ const typebox = document.getElementById("typebox") as HTMLElement; replybox.classList.add("hideReplyBox"); typebox.classList.remove("typeboxreplying"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable ="false"; - (document.getElementById("upload") as HTMLElement).style.visibility="hidden"; - (document.getElementById("typediv") as HTMLElement).style.visibility="hidden"; - (document.getElementById("sideDiv") as HTMLElement).innerHTML=""; + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "false"; + (document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; + (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; + (document.getElementById("sideDiv") as HTMLElement).innerHTML = ""; } - noChannel(addstate:boolean){ - if(addstate){ - history.pushState([this.id,undefined], "", "/channels/" + this.id); + noChannel(addstate: boolean) { + if (addstate) { + history.pushState([this.id, undefined], "", "/channels/" + this.id); } this.localuser.pageTitle(I18n.getTranslation("guild.emptytitle")); const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; @@ -811,82 +874,90 @@ class Guild extends SnowFlake{ this.localuser.getSidePannel(); const messages = document.getElementById("channelw") as HTMLDivElement; - for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){ + for (const thing of Array.from(messages.getElementsByClassName("messagecontainer"))) { thing.remove(); } - const h1=document.createElement("h1"); - h1.classList.add("messagecontainer") - h1.textContent=I18n.getTranslation("guild.emptytext"); + const h1 = document.createElement("h1"); + h1.classList.add("messagecontainer"); + h1.textContent = I18n.getTranslation("guild.emptytext"); messages.append(h1); } - loadGuild(){ + loadGuild() { this.localuser.loadGuild(this.id); } - updateChannel(json: channeljson){ + updateChannel(json: channeljson) { const channel = this.localuser.channelids.get(json.id); - if(channel){ + if (channel) { channel.updateChannel(json); this.headchannels = []; - for(const thing of this.channels){ + for (const thing of this.channels) { thing.children = []; } this.headchannels = []; - for(const thing of this.channels){ + for (const thing of this.channels) { const parent = thing.resolveparent(this); - if(!parent){ + if (!parent) { this.headchannels.push(thing); } } this.printServers(); } } - createChannelpac(json: channeljson){ + createChannelpac(json: channeljson) { const thischannel = new Channel(json, this); this.localuser.channelids.set(json.id, thischannel); this.channels.push(thischannel); thischannel.resolveparent(this); - if(!thischannel.parent){ + if (!thischannel.parent) { this.headchannels.push(thischannel); } this.calculateReorder(); this.printServers(); return thischannel; } - createchannels(func = this.createChannel){ - const options=["text", "announcement","voice"].map(e=>I18n.getTranslation("channel."+e)); + createchannels(func = this.createChannel) { + const options = ["text", "announcement", "voice"].map((e) => + I18n.getTranslation("channel." + e), + ); - const channelselect=new Dialog(""); - const form=channelselect.options.addForm("",(e:any)=>{ - func(e.name,e.type); + const channelselect = new Dialog(""); + const form = channelselect.options.addForm("", (e: any) => { + func(e.name, e.type); channelselect.hide(); }); - form.addSelect(I18n.getTranslation("channel.selectType"),"type",options,{radio:true},[0,5,2]); - form.addTextInput(I18n.getTranslation("channel.selectName"),"name"); + form.addSelect( + I18n.getTranslation("channel.selectType"), + "type", + options, + {radio: true}, + [0, 5, 2], + ); + form.addTextInput(I18n.getTranslation("channel.selectName"), "name"); channelselect.show(); } - createcategory(){ + createcategory() { const category = 4; - const channelselect=new Dialog(""); - const options=channelselect.options; - const form=options.addForm("",(e:any)=>{ + const channelselect = new Dialog(""); + const options = channelselect.options; + const form = options.addForm("", (e: any) => { this.createChannel(e.name, category); channelselect.hide(); }); - form.addTextInput(I18n.getTranslation("channel.selectCatName"),"name"); + form.addTextInput(I18n.getTranslation("channel.selectCatName"), "name"); channelselect.show(); } - delChannel(json: channeljson){ + delChannel(json: channeljson) { const channel = this.localuser.channelids.get(json.id); this.localuser.channelids.delete(json.id); - if(!channel)return; + if (!channel) return; this.channels.splice(this.channels.indexOf(channel), 1); const indexy = this.headchannels.indexOf(channel); - if(indexy !== -1){ + if (indexy !== -1) { this.headchannels.splice(indexy, 1); } - if(channel===this.prevchannel){ - this.prevchannel=undefined; + if (channel === this.prevchannel) { + this.prevchannel = undefined; } /* const build=[]; @@ -905,35 +976,32 @@ class Guild extends SnowFlake{ */ this.printServers(); } - createChannel(name: string, type: number){ + createChannel(name: string, type: number) { fetch(this.info.api + "/guilds/" + this.id + "/channels", { method: "POST", headers: this.headers, - body: JSON.stringify({ name, type }), + body: JSON.stringify({name, type}), }); } - async createRole(name: string){ - const fetched = await fetch( - this.info.api + "/guilds/" + this.id + "roles", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name, - color: 0, - permissions: "0", - }), - } - ); + async createRole(name: string) { + const fetched = await fetch(this.info.api + "/guilds/" + this.id + "roles", { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + color: 0, + permissions: "0", + }), + }); const json = await fetched.json(); const role = new Role(json, this); this.roleids.set(role.id, role); this.roles.push(role); return role; } - async updateRolePermissions(id: string, perms: Permissions){ + async updateRolePermissions(id: string, perms: Permissions) { const role = this.roleids.get(id); - if(!role){ + if (!role) { return; } role.permissions.allow = perms.allow; @@ -955,4 +1023,4 @@ class Guild extends SnowFlake{ } } Guild.setupcontextmenu(); -export{ Guild }; +export {Guild}; diff --git a/src/webpage/home.html b/src/webpage/home.html index 34347044..c1aa873e 100644 --- a/src/webpage/home.html +++ b/src/webpage/home.html @@ -1,39 +1,44 @@ - + - - - + + Jank Client - - - - - - - + + + + + + + - +
-

Welcome to Jank Client

-

Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:

+

+ Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many + features including: +

  • Direct Messaging
  • Reactions support
  • @@ -47,18 +52,18 @@

    Welcome to Jank Client

Spacebar-Compatible Instances:

-
-
+

Contribute to Jank Client

-

We always appreciate some help, whether that be in the form of bug reports, code, help translate, or even just pointing out some typos.


- - Github - +

+ We always appreciate some help, whether that be in the form of bug reports, code, help + translate, or even just pointing out some typos. +

+
+ Github
- diff --git a/src/webpage/home.ts b/src/webpage/home.ts index 20cac876..7be581b7 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -1,40 +1,56 @@ -import { I18n } from "./i18n.js"; -import{ mobile }from"./utils/utils.js"; +import {I18n} from "./i18n.js"; +import {mobile} from "./utils/utils.js"; console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; -(async ()=>{ +(async () => { await I18n.done; - const openClient=document.getElementById("openClient") - const welcomeJank=document.getElementById("welcomeJank") - const box1title=document.getElementById("box1title") - const box1Items=document.getElementById("box1Items") - const compatableInstances=document.getElementById("compatableInstances") - const box3title=document.getElementById("box3title") - const box3description=document.getElementById("box3description") - if(openClient&&welcomeJank&&compatableInstances&&box3title&&box3description&&box1title&&box1Items){ - openClient.textContent=I18n.getTranslation("htmlPages.openClient"); - welcomeJank.textContent=I18n.getTranslation("htmlPages.welcomeJank"); - box1title.textContent=I18n.getTranslation("htmlPages.box1title"); + const openClient = document.getElementById("openClient"); + const welcomeJank = document.getElementById("welcomeJank"); + const box1title = document.getElementById("box1title"); + const box1Items = document.getElementById("box1Items"); + const compatableInstances = document.getElementById("compatableInstances"); + const box3title = document.getElementById("box3title"); + const box3description = document.getElementById("box3description"); + if ( + openClient && + welcomeJank && + compatableInstances && + box3title && + box3description && + box1title && + box1Items + ) { + openClient.textContent = I18n.getTranslation("htmlPages.openClient"); + welcomeJank.textContent = I18n.getTranslation("htmlPages.welcomeJank"); + box1title.textContent = I18n.getTranslation("htmlPages.box1title"); - compatableInstances.textContent=I18n.getTranslation("htmlPages.compatableInstances"); - box3title.textContent=I18n.getTranslation("htmlPages.box3title"); - box3description.textContent=I18n.getTranslation("htmlPages.box3description"); + compatableInstances.textContent = I18n.getTranslation("htmlPages.compatableInstances"); + box3title.textContent = I18n.getTranslation("htmlPages.box3title"); + box3description.textContent = I18n.getTranslation("htmlPages.box3description"); - const items=I18n.getTranslation("htmlPages.box1Items").split("|"); - let i=0; + const items = I18n.getTranslation("htmlPages.box1Items").split("|"); + let i = 0; //@ts-ignore ts is being dumb here - for(const item of box1Items.children){ - (item as HTMLElement).textContent=items[i]; + for (const item of box1Items.children) { + (item as HTMLElement).textContent = items[i]; i++; } - }else{ - console.error(openClient,welcomeJank,compatableInstances,box3title,box3description,box1title,box1Items) + } else { + console.error( + openClient, + welcomeJank, + compatableInstances, + box3title, + box3description, + box1title, + box1Items, + ); } -})() +})(); fetch("/instances.json") - .then(_=>_.json()) + .then((_) => _.json()) .then( async ( json: { @@ -45,76 +61,77 @@ fetch("/instances.json") url?: string; display?: boolean; online?: boolean; - uptime: { alltime: number; daytime: number; weektime: number }; + uptime: {alltime: number; daytime: number; weektime: number}; urls: { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; }; - }[] - )=>{ + }[], + ) => { await I18n.done; console.warn(json); - for(const instance of json){ - if(instance.display === false){ + for (const instance of json) { + if (instance.display === false) { continue; } const div = document.createElement("div"); div.classList.add("flexltr", "instance"); - if(instance.image){ + if (instance.image) { const img = document.createElement("img"); img.src = instance.image; div.append(img); } const statbox = document.createElement("div"); - statbox.classList.add("flexttb","flexgrow"); + statbox.classList.add("flexttb", "flexgrow"); { const textbox = document.createElement("div"); textbox.classList.add("flexttb", "instancetextbox"); const title = document.createElement("h2"); title.innerText = instance.name; - if(instance.online !== undefined){ + if (instance.online !== undefined) { const status = document.createElement("span"); status.innerText = instance.online ? "Online" : "Offline"; status.classList.add("instanceStatus"); title.append(status); } textbox.append(title); - if(instance.description || instance.descriptionLong){ + if (instance.description || instance.descriptionLong) { const p = document.createElement("p"); - if(instance.descriptionLong){ + if (instance.descriptionLong) { p.innerText = instance.descriptionLong; - }else if(instance.description){ + } else if (instance.description) { p.innerText = instance.description; } textbox.append(p); } statbox.append(textbox); } - if(instance.uptime){ + if (instance.uptime) { const stats = document.createElement("div"); stats.classList.add("flexltr"); const span = document.createElement("span"); - span.innerText = I18n.getTranslation("home.uptimeStats",Math.round( - instance.uptime.alltime * 100 - )+"",Math.round( - instance.uptime.weektime * 100 - )+"",Math.round(instance.uptime.daytime * 100)+"") + span.innerText = I18n.getTranslation( + "home.uptimeStats", + Math.round(instance.uptime.alltime * 100) + "", + Math.round(instance.uptime.weektime * 100) + "", + Math.round(instance.uptime.daytime * 100) + "", + ); stats.append(span); statbox.append(stats); } div.append(statbox); - div.onclick = _=>{ - if(instance.online){ + div.onclick = (_) => { + if (instance.online) { window.location.href = "/register.html?instance=" + encodeURI(instance.name); - }else{ + } else { alert(I18n.getTranslation("home.warnOffiline")); } }; serverbox.append(div); } - } + }, ); diff --git a/src/webpage/hover.ts b/src/webpage/hover.ts index aa02bbd1..30f53a62 100644 --- a/src/webpage/hover.ts +++ b/src/webpage/hover.ts @@ -1,57 +1,58 @@ -import { Contextmenu } from "./contextmenu.js"; -import { MarkDown } from "./markdown.js"; +import {Contextmenu} from "./contextmenu.js"; +import {MarkDown} from "./markdown.js"; -class Hover{ - str:string|MarkDown|(()=>Promise|MarkDown|string); - constructor(txt:string|MarkDown|(()=>Promise|MarkDown|string)){ - this.str=txt; - } - addEvent(elm:HTMLElement){ - let timeOut=setTimeout(()=>{},0); - let elm2=document.createElement("div"); - elm.addEventListener("mouseover",()=>{ - timeOut=setTimeout(async ()=>{ - elm2=await this.makeHover(elm); - },750) - }); - elm.addEventListener("mouseout",()=>{ - clearTimeout(timeOut); - elm2.remove(); - }); - new MutationObserver(function (e) { - if (e[0].removedNodes) { - clearTimeout(timeOut); - elm2.remove(); - }; - }).observe(elm,{ childList: true }); - } - async makeHover(elm:HTMLElement){ - if(!document.contains(elm)) return document.createDocumentFragment() as unknown as HTMLDivElement; - const div=document.createElement("div"); - if(this.str instanceof MarkDown){ - div.append(this.str.makeHTML()) - }else if(this.str instanceof Function){ - const hover=await this.str(); - if(hover instanceof MarkDown){ - div.append(hover.makeHTML()); - }else{ - div.innerText=hover; - } - }else{ - div.innerText=this.str; - } - const box=elm.getBoundingClientRect(); - div.style.top=(box.bottom+4)+"px"; - div.style.left=Math.floor(box.left+box.width/2)+"px"; - div.classList.add("hoverthing"); - div.style.opacity="0"; - setTimeout(()=>{ - div.style.opacity="1"; - },10) - document.body.append(div); - Contextmenu.keepOnScreen(div); - console.log(div,elm); - return div; - } +class Hover { + str: string | MarkDown | (() => Promise | MarkDown | string); + constructor(txt: string | MarkDown | (() => Promise | MarkDown | string)) { + this.str = txt; + } + addEvent(elm: HTMLElement) { + let timeOut = setTimeout(() => {}, 0); + let elm2 = document.createElement("div"); + elm.addEventListener("mouseover", () => { + timeOut = setTimeout(async () => { + elm2 = await this.makeHover(elm); + }, 750); + }); + elm.addEventListener("mouseout", () => { + clearTimeout(timeOut); + elm2.remove(); + }); + new MutationObserver(function (e) { + if (e[0].removedNodes) { + clearTimeout(timeOut); + elm2.remove(); + } + }).observe(elm, {childList: true}); + } + async makeHover(elm: HTMLElement) { + if (!document.contains(elm)) + return document.createDocumentFragment() as unknown as HTMLDivElement; + const div = document.createElement("div"); + if (this.str instanceof MarkDown) { + div.append(this.str.makeHTML()); + } else if (this.str instanceof Function) { + const hover = await this.str(); + if (hover instanceof MarkDown) { + div.append(hover.makeHTML()); + } else { + div.innerText = hover; + } + } else { + div.innerText = this.str; + } + const box = elm.getBoundingClientRect(); + div.style.top = box.bottom + 4 + "px"; + div.style.left = Math.floor(box.left + box.width / 2) + "px"; + div.classList.add("hoverthing"); + div.style.opacity = "0"; + setTimeout(() => { + div.style.opacity = "1"; + }, 10); + document.body.append(div); + Contextmenu.keepOnScreen(div); + console.log(div, elm); + return div; + } } -export{Hover} +export {Hover}; diff --git a/src/webpage/i18n.ts b/src/webpage/i18n.ts index 742ba328..8ae5f204 100644 --- a/src/webpage/i18n.ts +++ b/src/webpage/i18n.ts @@ -1,122 +1,118 @@ //@ts-ignore import {langs} from "./translations/langs.js"; -const langmap=new Map(); -for(const lang of Object.keys(langs) as string[]){ - langmap.set(lang,langs[lang]); +const langmap = new Map(); +for (const lang of Object.keys(langs) as string[]) { + langmap.set(lang, langs[lang]); } console.log(langs); -type translation={ - [key:string]:string|translation +type translation = { + [key: string]: string | translation; }; -let res:()=>unknown=()=>{}; -class I18n{ - static lang:string; - static translations:translation[]=[]; - static done=new Promise((res2,_reject)=>{ - res=res2; - }); - static async create(lang:string){ +let res: () => unknown = () => {}; +class I18n { + static lang: string; + static translations: translation[] = []; + static done = new Promise((res2, _reject) => { + res = res2; + }); + static async create(lang: string) { + const json = (await (await fetch("/translations/" + lang + ".json")).json()) as translation; + const translations: translation[] = []; + translations.push(json); + if (lang !== "en") { + translations.push((await (await fetch("/translations/en.json")).json()) as translation); + } + this.lang = lang; + this.translations = translations; - const json=await (await fetch("/translations/"+lang+".json")).json() as translation; - const translations:translation[]=[]; - translations.push(json); - if(lang!=="en"){ - translations.push(await (await fetch("/translations/en.json")).json() as translation); - } - this.lang=lang; - this.translations=translations; + res(); + } + static getTranslation(msg: string, ...params: string[]): string { + let str: string | undefined; + const path = msg.split("."); + for (const json of this.translations) { + let jsont: string | translation = json; + for (const thing of path) { + if (typeof jsont !== "string" && jsont !== undefined) { + jsont = jsont[thing]; + } else { + jsont = json; + break; + } + } - res(); - } - static getTranslation(msg:string,...params:string[]):string{ - let str:string|undefined; - const path=msg.split("."); - for(const json of this.translations){ - let jsont:string|translation=json; - for(const thing of path){ - if(typeof jsont !== "string" && jsont!==undefined){ - jsont=jsont[thing]; + if (typeof jsont === "string") { + str = jsont; + break; + } + } + if (str) { + return this.fillInBlanks(str, params); + } else { + throw new Error(msg + " not found"); + } + } + static fillInBlanks(msg: string, params: string[]): string { + //thanks to geotale for the regex + msg = msg.replace(/\$\d+/g, (match) => { + const number = Number(match.slice(1)); + if (params[number - 1]) { + return params[number - 1]; + } else { + return match; + } + }); + msg = msg.replace(/{{(.+?)}}/g, (str, match: string) => { + const [op, strsSplit] = this.fillInBlanks(match, params).split(":"); + const [first, ...strs] = strsSplit.split("|"); + switch (op.toUpperCase()) { + case "PLURAL": { + const numb = Number(first); + if (numb === 0) { + return strs[strs.length - 1]; + } + return strs[Math.min(strs.length - 1, numb - 1)]; + } + case "GENDER": { + if (first === "male") { + return strs[0]; + } else if (first === "female") { + return strs[1]; + } else if (first === "neutral") { + if (strs[2]) { + return strs[2]; + } else { + return strs[0]; + } + } + } + } + return str; + }); - }else{ - jsont=json; - break; - } - } - - if(typeof jsont === "string"){ - str=jsont; - break; - } - } - if(str){ - return this.fillInBlanks(str,params); - }else{ - throw new Error(msg+" not found") - } - } - static fillInBlanks(msg:string,params:string[]):string{ - //thanks to geotale for the regex - msg=msg.replace(/\$\d+/g,(match) => { - const number=Number(match.slice(1)); - if(params[number-1]){ - return params[number-1]; - }else{ - return match; - } - }); - msg=msg.replace(/{{(.+?)}}/g, - (str, match:string) => { - const [op,strsSplit]=this.fillInBlanks(match,params).split(":"); - const [first,...strs]=strsSplit.split("|"); - switch(op.toUpperCase()){ - case "PLURAL":{ - const numb=Number(first); - if(numb===0){ - return strs[strs.length-1]; - } - return strs[Math.min(strs.length-1,numb-1)]; - } - case "GENDER":{ - if(first==="male"){ - return strs[0]; - }else if(first==="female"){ - return strs[1]; - }else if(first==="neutral"){ - if(strs[2]){ - return strs[2]; - }else{ - return strs[0]; - } - } - } - } - return str; - } - ); - - return msg; - } - static options(){ - return [...langmap.keys()].map(e=>e.replace(".json","")); - } - static setLanguage(lang:string){ - if(this.options().indexOf(userLocale)!==-1){ - localStorage.setItem("lang",lang); - I18n.create(lang); - } - } + return msg; + } + static options() { + return [...langmap.keys()].map((e) => e.replace(".json", "")); + } + static setLanguage(lang: string) { + if (this.options().indexOf(userLocale) !== -1) { + localStorage.setItem("lang", lang); + I18n.create(lang); + } + } } console.log(langmap); -let userLocale = navigator.language.slice(0,2) || "en"; -if(I18n.options().indexOf(userLocale)===-1){ - userLocale="en"; +let userLocale = navigator.language.slice(0, 2) || "en"; +if (I18n.options().indexOf(userLocale) === -1) { + userLocale = "en"; } -const storage=localStorage.getItem("lang"); -if(storage){ - userLocale=storage; -}else{ - localStorage.setItem("lang",userLocale) +const storage = localStorage.getItem("lang"); +if (storage) { + userLocale = storage; +} else { + localStorage.setItem("lang", userLocale); } I18n.create(userLocale); -export{I18n,langmap}; +export {I18n, langmap}; diff --git a/src/webpage/index.html b/src/webpage/index.html index 114e9177..584d0275 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -1,23 +1,37 @@ - + - - + + Jank Client - - - - - - - - + + + + + + + +
- +

Jank Client is loading

This shouldn't take long

Switch Accounts

@@ -36,7 +50,7 @@

Server Name

- +

USERNAME

@@ -55,8 +69,8 @@

Server Name

- - + + Channel name @@ -64,15 +78,14 @@

Server Name

- +
-
-
+
-
+
@@ -81,7 +94,7 @@

Server Name

-
+