From a2e19f8d3880bfa4fdf0de93fbe21319565a858a Mon Sep 17 00:00:00 2001 From: "Frederick [Bot]" Date: Fri, 27 Mar 2026 01:22:35 +0000 Subject: [PATCH 001/501] chore(i18n): update translations via Crowdin --- frontend/src/i18n/lang/ar-SA.json | 9 +-------- frontend/src/i18n/lang/bg-BG.json | 9 +-------- frontend/src/i18n/lang/cs-CZ.json | 9 +-------- frontend/src/i18n/lang/da-DK.json | 9 +-------- frontend/src/i18n/lang/de-DE.json | 9 +-------- frontend/src/i18n/lang/de-swiss.json | 9 +-------- frontend/src/i18n/lang/es-ES.json | 9 +-------- frontend/src/i18n/lang/fi-FI.json | 8 +------- frontend/src/i18n/lang/fr-FR.json | 9 +-------- frontend/src/i18n/lang/he-IL.json | 9 +-------- frontend/src/i18n/lang/hr-HR.json | 9 +-------- frontend/src/i18n/lang/hu-HU.json | 9 +-------- frontend/src/i18n/lang/it-IT.json | 9 +-------- frontend/src/i18n/lang/ja-JP.json | 9 +-------- frontend/src/i18n/lang/ko-KR.json | 9 +-------- frontend/src/i18n/lang/lt-LT.json | 9 +-------- frontend/src/i18n/lang/nl-NL.json | 9 +-------- frontend/src/i18n/lang/no-NO.json | 9 +-------- frontend/src/i18n/lang/pl-PL.json | 9 +-------- frontend/src/i18n/lang/pt-BR.json | 9 +-------- frontend/src/i18n/lang/pt-PT.json | 9 +-------- frontend/src/i18n/lang/ru-RU.json | 19 ++++++++++--------- frontend/src/i18n/lang/sl-SI.json | 9 +-------- frontend/src/i18n/lang/sv-SE.json | 9 +-------- frontend/src/i18n/lang/tr-TR.json | 9 +-------- frontend/src/i18n/lang/uk-UA.json | 9 +-------- frontend/src/i18n/lang/vi-VN.json | 9 +-------- frontend/src/i18n/lang/zh-CN.json | 9 +-------- frontend/src/i18n/lang/zh-TW.json | 9 +-------- 29 files changed, 38 insertions(+), 232 deletions(-) diff --git a/frontend/src/i18n/lang/ar-SA.json b/frontend/src/i18n/lang/ar-SA.json index f6f0159cc..c516e0ebd 100644 --- a/frontend/src/i18n/lang/ar-SA.json +++ b/frontend/src/i18n/lang/ar-SA.json @@ -96,15 +96,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "يمكنك توصيل Vikunja إلى خدمة CalDAV لعرض وإدارة جميع المهام. أدخل عنوان Url في المنصة:", "more": "معلومات إضافية عن CalDAV في Vikunja", - "tokens": "رموز CalDAV", - "tokensHowTo": "يمكنك استخدام رمز CalDAV لاستخدامه بدلاً من كلمة المرور لتسجيل الدخول في عنوان الموقع أعلاه.", - "createToken": "إنشاء رمز مميز", - "tokenCreated": "إليك الرمز المميز الخاص بك: {token}", - "wontSeeItAgain": "قم بكتابته وحفظه في مكان آمن، لن تتمكن من عرضه مرة أخرى.", - "mustUseToken": "تحتاج إلى إنشاء رمز CalDAV إذا كنت ترغب باستخدام CalDAV مع منصة طرف ثالث. استخدم الرمز ككلمة المرور.", - "usernameIs": "اسم المستخدم الخاص بك هو: {0}" + "tokens": "رموز CalDAV" }, "avatar": { "title": "الصورة الرمزية", diff --git a/frontend/src/i18n/lang/bg-BG.json b/frontend/src/i18n/lang/bg-BG.json index 1c587c24b..7874c5424 100644 --- a/frontend/src/i18n/lang/bg-BG.json +++ b/frontend/src/i18n/lang/bg-BG.json @@ -116,15 +116,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Можете да свържете Vikunja с CalDAV клиенти, за да преглеждате и управлявате всички задачи от различни клиенти. Въведете този URL адрес във вашия клиент:", "more": "Повече информация за CalDAV във Vikunja", - "tokens": "CalDAV токени", - "tokensHowTo": "Можете да използвате CalDAV токен вместо парола за вход в горната крайна точка.", - "createToken": "Създаване на токен", - "tokenCreated": "Ето вашия токен: {token}", - "wontSeeItAgain": "Запишете го, няма да можете да го видите отново.", - "mustUseToken": "Трябва да създадете CalDAV токен, ако искате да използвате CalDAV с клиент на трета страна. Използвайте токена като парола.", - "usernameIs": "Вашето потребителско име е: {0}" + "tokens": "CalDAV токени" }, "avatar": { "title": "Аватар", diff --git a/frontend/src/i18n/lang/cs-CZ.json b/frontend/src/i18n/lang/cs-CZ.json index e5dcf2b15..f0b9602b5 100644 --- a/frontend/src/i18n/lang/cs-CZ.json +++ b/frontend/src/i18n/lang/cs-CZ.json @@ -147,15 +147,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Můžete propojit Vikunja s klienty CalDAV a prohlížet a spravovat všechny úkoly v nich. Zadejte tuto url do klienta:", "more": "Více informací o CalDAV ve Vikunja", - "tokens": "CalDAV tokeny", - "tokensHowTo": "Pro přihlášení do výše uvedeného koncového bodu můžete použít CalDAV token místo hesla.", - "createToken": "Vytvořit token", - "tokenCreated": "Tady je váš token: {token}", - "wontSeeItAgain": "Poznamenejte si ho, znovu ho už neuvidíte.", - "mustUseToken": "Musíte vytvořit CalDAV token, pokud chcete používat CalDAV s klientem třetí strany. Jako heslo použijte token.", - "usernameIs": "Vaše uživatelské jméno je: {0}" + "tokens": "CalDAV tokeny" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/da-DK.json b/frontend/src/i18n/lang/da-DK.json index 6c74b8eb4..cfb672f59 100644 --- a/frontend/src/i18n/lang/da-DK.json +++ b/frontend/src/i18n/lang/da-DK.json @@ -83,15 +83,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Du kan forbinde Vikunja til CalDAV klienter for at vise og administrere alle opgaver fra forskellige klienter. Indtast dette url i din klient:", "more": "Mere information om CalDAV i Vikunja", - "tokens": "CalDAV Tokens", - "tokensHowTo": "Du kan bruge et CalDAV-token i stedet for en adgangskode til at logge på ovenstående slutpunkt.", - "createToken": "Opret et token", - "tokenCreated": "Her er dit token: {token}", - "wontSeeItAgain": "Skriv det ned; du vil ikke være i stand til at få det vist igen.", - "mustUseToken": "Du skal oprette et CalDAV-token hvis du ønsker at bruge CalDAV med en tredjeparts klient. Brug token som adgangskode.", - "usernameIs": "Dit brugernavn er: {0}" + "tokens": "CalDAV Tokens" }, "avatar": { "title": "Profilbillede", diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json index d2dea6e46..f48d05a29 100644 --- a/frontend/src/i18n/lang/de-DE.json +++ b/frontend/src/i18n/lang/de-DE.json @@ -147,15 +147,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Du kannst Vikunja mit CalDAV-Clients verbinden, um alle Aufgaben mit verschiedenen Clients anzuzeigen und zu verwalten. Gebe dazu diese Url in deine Client ein:", "more": "Weitere Informationen über CalDAV in Vikunja", - "tokens": "CalDAV-Token", - "tokensHowTo": "Du kannst für den obigen Endpunkt einen CalDAV Token anstelle deines Passwortes verwenden.", - "createToken": "Token erstellen", - "tokenCreated": "Hier ist dein Token: {token}", - "wontSeeItAgain": "Notiere dir den Token, er kann nicht wieder angezeigt werden.", - "mustUseToken": "Du musst einen CalDAV Token erstellen um CalDAV mit einem Drittanbieter-Client zu nutzen. Verwende diesen Token anstelle deines Passworts.", - "usernameIs": "Dein Benutzername lautet: {0}" + "tokens": "CalDAV-Token" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json index c6c20f879..b735c0e4a 100644 --- a/frontend/src/i18n/lang/de-swiss.json +++ b/frontend/src/i18n/lang/de-swiss.json @@ -147,15 +147,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Du kannst Vikunja mit CalDAV-Clients verbinden, um alle Aufgaben mit verschiedenen Clients anzuzeigen und zu verwalten. Gebe dazu diese Url in deine Client ein:", "more": "Weitere Informationen über CalDAV in Vikunja", - "tokens": "CalDAV-Token", - "tokensHowTo": "Du kannst für den obigen Endpunkt einen CalDAV Token anstelle deines Passwortes verwenden.", - "createToken": "Token erstellen", - "tokenCreated": "Hier ist dein Token: {token}", - "wontSeeItAgain": "Notiere dir den Token, er kann nicht wieder angezeigt werden.", - "mustUseToken": "Du musst einen CalDAV Token erstellen um CalDAV mit einem Drittanbieter-Client zu nutzen. Verwende diesen Token anstelle deines Passworts.", - "usernameIs": "Dein Benutzername lautet: {0}" + "tokens": "CalDAV-Token" }, "avatar": { "title": "Herr Der Elemente", diff --git a/frontend/src/i18n/lang/es-ES.json b/frontend/src/i18n/lang/es-ES.json index 2c81d5bdf..a5c4fa3fe 100644 --- a/frontend/src/i18n/lang/es-ES.json +++ b/frontend/src/i18n/lang/es-ES.json @@ -91,15 +91,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Puedes conectar Vikunja a clientes CalDAV para ver y gestionar todas las tareas desde diferentes clientes. Introduce esta URL en tu cliente:", "more": "Más información sobre CalDAV en Vikunja", - "tokens": "Tokens CalDAV", - "tokensHowTo": "Puedes utilizar un token de CalDAV en lugar de una contraseña para iniciar sesión en el 'endpoint' anterior.", - "createToken": "Crear un token", - "tokenCreated": "Aquí está tu token: {token}", - "wontSeeItAgain": "Anótalo, no podrás verlo de nuevo.", - "mustUseToken": "Necesitas crear un token de CalDAV si desesa utilizar CalDAV con un cliente externo. Utiliza el token como contraseña.", - "usernameIs": "Tu nombre de usuario es: {0}" + "tokens": "Tokens CalDAV" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/fi-FI.json b/frontend/src/i18n/lang/fi-FI.json index 8944aad2c..23afa90a3 100644 --- a/frontend/src/i18n/lang/fi-FI.json +++ b/frontend/src/i18n/lang/fi-FI.json @@ -139,14 +139,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Voit yhdistää Vikunjan CalDAV asiakasohjelmiin nähdäksesi ja hallitaksesi kaikkia tehtäviä eri asiakasohjelmilla. Syötä tämä url asiakasohjelmaasi:", "more": "Lisätietoja CalDAVista Vikunjassa", - "tokens": "CalDAV Tokenit", - "createToken": "Luo tokeni", - "tokenCreated": "Tässä on tokenisi: {token}", - "wontSeeItAgain": "Kirjoita se ylös, et voi nähdä sitä uudelleen.", - "mustUseToken": "Sinun täytyy luoda CalDAV token, jos haluat käyttää CalDAVia kolmannen osapuolen asiakasohjelman kanssa. Käytä tokenia salasanana.", - "usernameIs": "Käyttäjänimesi on: {0}" + "tokens": "CalDAV Tokenit" }, "avatar": { "title": "Profiilikuva", diff --git a/frontend/src/i18n/lang/fr-FR.json b/frontend/src/i18n/lang/fr-FR.json index 91ca3d9cd..e920c15a8 100644 --- a/frontend/src/i18n/lang/fr-FR.json +++ b/frontend/src/i18n/lang/fr-FR.json @@ -137,15 +137,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Vous pouvez connecter Vikunja à des clients CalDAV pour visualiser et gérer toutes les tâches de différents clients. Saisissez cette adresse dans votre client :", "more": "Plus d'informations sur CalDAV dans Vikunja", - "tokens": "Jetons CalDAV", - "tokensHowTo": "Vous pouvez utiliser un jeton CalDAV à la place d’un mot de passe pour vous connecter au point de terminaison ci-dessus.", - "createToken": "Créer un jeton", - "tokenCreated": "Voici votre jeton : {token}", - "wontSeeItAgain": "Écrivez-le, vous ne pourrez plus le revoir.", - "mustUseToken": "Vous devez créer un jeton CalDAV si vous voulez utiliser CalDAV avec un client tiers. Utilisez le jeton comme mot de passe.", - "usernameIs": "Votre nom d’utilisateur ou d’utilisatrice est : {0}" + "tokens": "Jetons CalDAV" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/he-IL.json b/frontend/src/i18n/lang/he-IL.json index 6ac27e7ba..7db3591d5 100644 --- a/frontend/src/i18n/lang/he-IL.json +++ b/frontend/src/i18n/lang/he-IL.json @@ -118,15 +118,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "ניתן לחבר Vikunja לקליינטים CalDEV כדי לצפות ולנהל כל המטלות מקליינטים שונים. הכנס url זה לקליינט שלך:", "more": "עוד מידע על CalDAV ב-Vikunja", - "tokens": "אסימוני CalDAV", - "tokensHowTo": "ניתן להשתמש באסימון CalDAV במקום סיסמה כדי להיכנס ל-endpoint לעיל.", - "createToken": "ליצור אסימון", - "tokenCreated": "זה האסימון שלך: {token}", - "wontSeeItAgain": "לרשום אותו היטב, לא ניתן לראותו שוב.", - "mustUseToken": "חייב ליצור אסימון CalDAV אם ברצונך להשתמש ב-CalDAV עם גורם קליינט צד שלישי. יש להשתמש באסימון כסיסמה.", - "usernameIs": "שם משתמש שלך הוא: {0}" + "tokens": "אסימוני CalDAV" }, "avatar": { "title": "אווטאר", diff --git a/frontend/src/i18n/lang/hr-HR.json b/frontend/src/i18n/lang/hr-HR.json index 4f570235c..ece67823f 100644 --- a/frontend/src/i18n/lang/hr-HR.json +++ b/frontend/src/i18n/lang/hr-HR.json @@ -100,15 +100,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Vikunju možete povezati s CalDAV klijentima za pregled i upravljanje svim zadacima različitih klijenata. Unesite ovaj url u svoj klijent:", "more": "Više informacija o CalDAV-u u Vikunji", - "tokens": "CalDAV tokeni", - "tokensHowTo": "Možete koristiti CalDAV token umjesto lozinke za prijavu u gornju kranjnju točku.", - "createToken": "Stvorite token", - "tokenCreated": "Evo vašeg tokena: {token}", - "wontSeeItAgain": "Zapišite, nećete ga više moći vidjeti.", - "mustUseToken": "Morate izraditi CalDAV token ako želite koristiti CalDAV s klijentom treće strane. Koristite token kao lozinku.", - "usernameIs": "Vaše korisničko ime je: {0}" + "tokens": "CalDAV tokeni" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/hu-HU.json b/frontend/src/i18n/lang/hu-HU.json index 1723e5d93..4319a18af 100644 --- a/frontend/src/i18n/lang/hu-HU.json +++ b/frontend/src/i18n/lang/hu-HU.json @@ -100,15 +100,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "A Vikunja a CalDAV kliensekhez csatlakoztatható, hogy megtekinthesse és kezelhesse a különböző kliensektől származó összes feladatot. Írja be ezt az URL-t a kliensébe:", "more": "További információ a CalDAV-ról a Vikunjában", - "tokens": "CalDAV Tokenek", - "tokensHowTo": "Jelszó helyett CalDAV Token is használható a bejelentkezéshez a fenti végponton.", - "createToken": "Token létrehozása", - "tokenCreated": "Íme a tokenje: {token}", - "wontSeeItAgain": "Kérjük írja le, nem fogja tudni újra megnézni.", - "mustUseToken": "Létre kell hoznia egy CalDAV Tokent, ha harmadik féltől származó klienssel szeretné használni. Használja a tokent jelszóként.", - "usernameIs": "A felhasználó neve: {0}" + "tokens": "CalDAV Tokenek" }, "avatar": { "title": "Profilkép", diff --git a/frontend/src/i18n/lang/it-IT.json b/frontend/src/i18n/lang/it-IT.json index ee5ed18a5..416296c15 100644 --- a/frontend/src/i18n/lang/it-IT.json +++ b/frontend/src/i18n/lang/it-IT.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Puoi connettere Vikunja ai client CalDAV per visualizzare e gestire tutte le attività da diversi client. Inserisci questo URL nel client:", "more": "Ulteriori informazioni riguardo CalDAV in Vikunja", - "tokens": "Token CalDAV", - "tokensHowTo": "Puoi utilizzare un token CalDAV al posto di una password per accedere all'indirizzo sopra indicato.", - "createToken": "Crea un token", - "tokenCreated": "Ecco il token: {token}", - "wontSeeItAgain": "Scrivetelo, non potrai più vederlo in futuro.", - "mustUseToken": "È necessario creare un token CalDAV se desideri utilizzare CalDAV con un client di terze parti. Usa il token come password.", - "usernameIs": "Il tuo nome utente è: {0}" + "tokens": "Token CalDAV" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/ja-JP.json b/frontend/src/i18n/lang/ja-JP.json index 991a7aa9a..09c5d9e8c 100644 --- a/frontend/src/i18n/lang/ja-JP.json +++ b/frontend/src/i18n/lang/ja-JP.json @@ -145,15 +145,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "VikunjaをCalDAVクライアントと連携することでさまざまなクライアントからのすべてのタスクを表示および管理できます。CalDAVクライアントから接続するには次のエンドポイントをコピーしてクライアントに入力します:", "more": "VikunjaのCalDAVに関する詳細情報", - "tokens": "CalDAVトークン", - "tokensHowTo": "上記のエンドポイントからログインする際にパスワードの代わりにCalDAVトークンを使用できます。", - "createToken": "トークンの生成", - "tokenCreated": "トークン: {token}", - "wontSeeItAgain": "このトークンをコピーしてください。二度と見ることができなくなります。", - "mustUseToken": "サードパーティーのクライアントからCalDAVを使用する場合、パスワードの代わりにCalDAVトークンを生成して使用する必要があります。", - "usernameIs": "ユーザー名: {0}" + "tokens": "CalDAVトークン" }, "avatar": { "title": "プロフィール画像", diff --git a/frontend/src/i18n/lang/ko-KR.json b/frontend/src/i18n/lang/ko-KR.json index db14a95c4..0d50e10ba 100644 --- a/frontend/src/i18n/lang/ko-KR.json +++ b/frontend/src/i18n/lang/ko-KR.json @@ -118,15 +118,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Vikunja를 CalDAV 클라이언트와 연계하여 다양한 클라이언트의 모든 할 일을 표시 및 관리할 수 있습니다. CalDAV 클라이언트에서 연결하려면 다음 엔드포인트를 복사하여 클라이언트에 입력합니다.", "more": "Vikunja의 CalDAV에 대한 자세한 정보", - "tokens": "CalDAV Tokens", - "tokensHowTo": "위의 엔드포인트에 로그인할 때 비밀번호 대신 CalDAV 토큰을 사용할 수 있습니다.", - "createToken": "토큰 생성하기", - "tokenCreated": "생성된 토큰 정보: {token}", - "wontSeeItAgain": "적어두세요 다시 볼 수 없습니다.", - "mustUseToken": "타사 클라이언트에서 CalDAV를 사용하려면 CalDAV 토큰을 만들어야 합니다. 토큰을 비밀번호로 사용합니다.", - "usernameIs": "당신의 사용자명은: {0}" + "tokens": "CalDAV Tokens" }, "avatar": { "title": "아바타", diff --git a/frontend/src/i18n/lang/lt-LT.json b/frontend/src/i18n/lang/lt-LT.json index 7b9f351b3..64a0316bb 100644 --- a/frontend/src/i18n/lang/lt-LT.json +++ b/frontend/src/i18n/lang/lt-LT.json @@ -117,15 +117,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Gali prijungti Vikunja prie CalDav klientų, kad būtų galima matyti ar valdyti visas užduotis iš skirtingų klientų. Įvesk šią nuorodą į savo paskyrą:", "more": "Daugiau informacijos apie CalDAV Vikunja", - "tokens": "CalDAV tokenai", - "tokensHowTo": "Galite naudoti CalDAV tokena vietoj slaptažodžio norėdami prisijungti prie paskyros.", - "createToken": "Sukurti prieigos raktą", - "tokenCreated": "Čia jūsų tokenas: {token}", - "wontSeeItAgain": "Užsirašyk, nes daugiau to nepamatysi.", - "mustUseToken": "Reikia susikurti CalDAV tokeną, jei norite naudoti CalDAV su trečiųjų šalių paskyromis. Naudokite tokeną, kaip slaptažodį.", - "usernameIs": "Tavo vartotojo vardas yra: {0}" + "tokens": "CalDAV tokenai" }, "avatar": { "title": "Avataras", diff --git a/frontend/src/i18n/lang/nl-NL.json b/frontend/src/i18n/lang/nl-NL.json index d93894cfa..368d4efad 100644 --- a/frontend/src/i18n/lang/nl-NL.json +++ b/frontend/src/i18n/lang/nl-NL.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Je kunt Vikunja verbinden met CalDAV-clients om alle taken van verschillende clients te bekijken en beheren. Voer deze url in bij je client:", "more": "Meer informatie over CalDAV in Vikunja", - "tokens": "CalDAV tokens", - "tokensHowTo": "Je kunt een CalDAV-token gebruiken in plaats van een wachtwoord om in te loggen op het bovenstaande eindpunt.", - "createToken": "Token aanmaken", - "tokenCreated": "Hier is je token: {token}", - "wontSeeItAgain": "Noteer het want je kunt het niet opnieuw bekijken.", - "mustUseToken": "Je moet een CalDAV-token aanmaken als je CalDAV wilt gebruiken met een externe applicatie. Gebruik het token als wachtwoord.", - "usernameIs": "Je gebruikersnaam is: {0}" + "tokens": "CalDAV tokens" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/no-NO.json b/frontend/src/i18n/lang/no-NO.json index 8cf3b2821..940e333b9 100644 --- a/frontend/src/i18n/lang/no-NO.json +++ b/frontend/src/i18n/lang/no-NO.json @@ -138,15 +138,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Du kan koble Vikunja til CalDAV-klienter for å se og administrere alle oppgaver fra forskjellige kunder. Skriv inn denne Url'en til din klient:", "more": "Mer informasjon om CalDAV i Vikunja", - "tokens": "CalDAV-plassholdere", - "tokensHowTo": "Du kan bruke en CalDAV-plassholder til å bruke istedet for passord for å logge på det ovennevnte endepunktet.", - "createToken": "Opprett plassholder", - "tokenCreated": "Her er din plassholder: {token}", - "wontSeeItAgain": "Skriv den ned, du vil ikke kunne se den igjen.", - "mustUseToken": "Du må opprette et CalDAV-plassholder hvis du ønsker å bruke CalDAV med en tredjeparts klient. Bruk plassholderen som passord.", - "usernameIs": "Brukernavnet ditt er: {0}" + "tokens": "CalDAV-plassholdere" }, "avatar": { "title": "Profilbilde", diff --git a/frontend/src/i18n/lang/pl-PL.json b/frontend/src/i18n/lang/pl-PL.json index 6aec65684..1c56882d7 100644 --- a/frontend/src/i18n/lang/pl-PL.json +++ b/frontend/src/i18n/lang/pl-PL.json @@ -102,15 +102,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Możesz połączyć Vikunję z klientami CalDAV, aby przeglądać i zarządzać wszystkimi zadaniami z różnych klientów. Wprowadź ten adres URL do swojego klienta:", "more": "Więcej informacji o CalDAV w Vikunji", - "tokens": "Tokeny CalDAV", - "tokensHowTo": "Możesz użyć tokenu CalDAV zamiast hasła do logowania się w powyższym punkcie końcowym.", - "createToken": "Utwórz token", - "tokenCreated": "Oto twój token: {token}", - "wontSeeItAgain": "Zapisz to, nie będziesz mógł tego zobaczyć ponownie.", - "mustUseToken": "Musisz utworzyć token CalDAV, jeśli chcesz używać CalDAV z klientem zewnętrznym. Użyj tokena jako hasła.", - "usernameIs": "Twoja nazwa użytkownika to: {0}" + "tokens": "Tokeny CalDAV" }, "avatar": { "title": "Awatar", diff --git a/frontend/src/i18n/lang/pt-BR.json b/frontend/src/i18n/lang/pt-BR.json index 46e7f3576..5fd42ffcb 100644 --- a/frontend/src/i18n/lang/pt-BR.json +++ b/frontend/src/i18n/lang/pt-BR.json @@ -97,15 +97,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Você pode conectar o Vikunja aos clientes de CalDAV para visualizar e gerenciar todas as tarefas de diferentes clientes. Digite esta url em seu cliente:", "more": "Mais informações sobre CalDAV em Vikunja", - "tokens": "CalDAV Tokens", - "tokensHowTo": "Você pode usar um token CalDAV em vez de uma senha para fazer o login no endpoint acima.", - "createToken": "Criar um token", - "tokenCreated": "Aqui está seu token: {token}", - "wontSeeItAgain": "Anote isso, você não poderá vê-lo novamente.", - "mustUseToken": "Você precisa criar um token CalDAV se quiser usar CalDAV com um cliente de terceiros. Use o token como a senha.", - "usernameIs": "Seu usuário é: {0}" + "tokens": "CalDAV Tokens" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/pt-PT.json b/frontend/src/i18n/lang/pt-PT.json index 846bb9e64..d9abfde91 100644 --- a/frontend/src/i18n/lang/pt-PT.json +++ b/frontend/src/i18n/lang/pt-PT.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Podes conectar o Vikunja a clientes CalDAV para visualizar e gerenciar todas as tarefas através de diferentes aplicativos. Coloca este url no teu aplicativo:", "more": "Mais informações sobre o CalDAV no Vikunja", - "tokens": "Tokens CalDAV", - "tokensHowTo": "Podes utilizar um token CalDAV em vez de uma palavra-passe para iniciar sessão no ponto de acesso acima.", - "createToken": "Criar um token", - "tokenCreated": "Aqui está o teu token: {token}", - "wontSeeItAgain": "Anota-o, não conseguirás visualiza-lo novamente.", - "mustUseToken": "Precisas criar um token CalDAV se quiseres utilizar CalDAV com um cliente de terceiros. Utiliza o token como palavra-passe.", - "usernameIs": "O teu nome de utilizador é: {0}" + "tokens": "Tokens CalDAV" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/ru-RU.json b/frontend/src/i18n/lang/ru-RU.json index b724e0230..d44475006 100644 --- a/frontend/src/i18n/lang/ru-RU.json +++ b/frontend/src/i18n/lang/ru-RU.json @@ -147,15 +147,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Вы можете подключить Vikunja к клиентам CalDAV, чтобы просматривать и управлять всеми задачами из разных клиентов. Введите этот url в клиенте:", "more": "Подробнее о CalDAV в Vikunja", - "tokens": "Токены CalDAV", - "tokensHowTo": "Вы можете использовать CalDAV токен вместо пароля для входа в вышеуказанную конечную точку.", - "createToken": "Создать токен", - "tokenCreated": "Ваш токен: {token}", - "wontSeeItAgain": "Запишите его где-нибудь. У вас больше не будет возможности его увидеть.", - "mustUseToken": "Вам необходимо создать токен CalDAV, если вы хотите использовать его со сторонним клиентом. Используйте его в качестве пароля.", - "usernameIs": "Ваше имя пользователя: {0}" + "tokens": "Токены CalDAV" }, "avatar": { "title": "Аватар", @@ -207,6 +200,13 @@ "tokenCreatedSuccess": "Ваш новый токен: {token}", "tokenCreatedNotSeeAgain": "Сохраните его в безопасном месте, вы не увидите его снова!", "selectAll": "Выбрать всё", + "presets": { + "title": "Быстрые предустановки", + "readOnly": "Только чтение", + "tasks": "Управление задачами", + "projects": "Управление проектами", + "fullAccess": "Полный доступ" + }, "delete": { "header": "Удалить этот токен", "text1": "Удалить токен «{token}»?", @@ -840,7 +840,8 @@ "select": "Выбрать диапазон дат", "noTasks": "Делать нечего — хорошего дня!", "filterByLabel": "Фильтрация по метке {label}", - "clearLabelFilter": "Убрать фильтрацию по метке" + "clearLabelFilter": "Убрать фильтрацию по метке", + "savedFilterIgnored": "Сохранённый фильтр, используемый на странице обзора, не применяется при просмотре задач по метке." }, "detail": { "chooseDueDate": "Нажмите для выбора срока", diff --git a/frontend/src/i18n/lang/sl-SI.json b/frontend/src/i18n/lang/sl-SI.json index 0b769bcc8..ad07ae494 100644 --- a/frontend/src/i18n/lang/sl-SI.json +++ b/frontend/src/i18n/lang/sl-SI.json @@ -116,15 +116,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Da si ogledate in upravljate vse naloge iz različnih odjemalcev, lahko Vikunjo povežete z odjemalci CalDAV. Vnesite ta URL v svojega odjemalca:", "more": "Več informacij o CalDAV v Vikunji", - "tokens": "CalDAV žetoni", - "tokensHowTo": "Namesto gesla za prijavo v zgornjo končno povezavo, lahko uporabite žeton CalDAV.", - "createToken": "Ustvarite žeton", - "tokenCreated": "Tu je vaš žeton: {token}", - "wontSeeItAgain": "Zapišite ga, ker ga ne boste več ponovno videli.", - "mustUseToken": "Če želite uporabljati CalDAV z drugim odjemalcem, morate ustvariti žeton CalDAV. Uporabite žeton namesto gesla.", - "usernameIs": "Vaše uporabniško ime je: {0}" + "tokens": "CalDAV žetoni" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/sv-SE.json b/frontend/src/i18n/lang/sv-SE.json index 589260a0a..13d679b23 100644 --- a/frontend/src/i18n/lang/sv-SE.json +++ b/frontend/src/i18n/lang/sv-SE.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Du kan ansluta Vikunja till CalDAV-klienter för att visa och hantera alla uppgifter från olika klienter. Ange denna URL i din klient:", "more": "Mer information om CalDAV i Vikunja", - "tokens": "CalDAV-token", - "tokensHowTo": "Du kan använda en CalDAV-token för att använda istället för ett lösenord för att logga in på ovanstående slutpunkt.", - "createToken": "Skapa ny token", - "tokenCreated": "Här är din token: {token}", - "wontSeeItAgain": "Skriv ner den, du kommer inte att kunna se den igen.", - "mustUseToken": "Du måste skapa en CalDAV-token om du vill använda CalDAV med en tredjepartsklient. Använd token som lösenord.", - "usernameIs": "Ditt användarnamn är: {0}" + "tokens": "CalDAV-token" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/tr-TR.json b/frontend/src/i18n/lang/tr-TR.json index a157eb077..17f8a9eaf 100644 --- a/frontend/src/i18n/lang/tr-TR.json +++ b/frontend/src/i18n/lang/tr-TR.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Vikunja’yı CalDAV istemcilerine bağlayarak tüm görevleri farklı istemcilerden görüntüleyebilir ve yönetebilirsiniz. İstemcinize bu URL’yi girin:", "more": "Vikunja’da CalDAV hakkında daha fazla bilgi", - "tokens": "CalDAV Token’ları", - "tokensHowTo": "Yukarıdaki uç noktada oturum açmak için şifre yerine bir CalDAV token’ı kullanabilirsiniz.", - "createToken": "Token oluştur", - "tokenCreated": "İşte token’ınız: {token}", - "wontSeeItAgain": "Bunu not edin, tekrar göremeyeceksiniz.", - "mustUseToken": "Üçüncü taraf bir istemciyle CalDAV kullanmak istiyorsanız bir CalDAV token’ı oluşturmanız gerekir. Token’ı şifre olarak kullanın.", - "usernameIs": "Kullanıcı adınız: {0}" + "tokens": "CalDAV Token’ları" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/uk-UA.json b/frontend/src/i18n/lang/uk-UA.json index 66f8aebb3..f2c62f18e 100644 --- a/frontend/src/i18n/lang/uk-UA.json +++ b/frontend/src/i18n/lang/uk-UA.json @@ -130,15 +130,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Можете під'єднати Vikunja до CalDAV, щоб оглядати та змінювати завдання з инших сервісів. Посилання для підхожого сервісу:", "more": "Дізнатися більше про CalDAV у Vikunja", - "tokens": "CalDAV Ключі", - "tokensHowTo": "Ви можете вжити CalDAV ключ замість пароля для входу за одержаним посиланням вище.", - "createToken": "Створити ключ", - "tokenCreated": "Ваш ключ: {token}", - "wontSeeItAgain": "Запишіть його та збережіть в надійному місці, бо він зникне і не з'явиться знову.", - "mustUseToken": "Слід створити CalDAV ключ, якщо хочете вживати CalDAV зі сторонніх сервісів. Вживайте ключ як пароль.", - "usernameIs": "Ваше ім'я вживача: {0}" + "tokens": "CalDAV Ключі" }, "avatar": { "title": "Зображення обліковки", diff --git a/frontend/src/i18n/lang/vi-VN.json b/frontend/src/i18n/lang/vi-VN.json index ac8593662..fa9cde2fc 100644 --- a/frontend/src/i18n/lang/vi-VN.json +++ b/frontend/src/i18n/lang/vi-VN.json @@ -116,15 +116,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "Bạn có thể kết nối Vikunja tới các khách hàng trên CalDAV để xem và quản lý các tác vụ từ nhiều khách hàng khác nhau. Nhập url vào phần khách hàng:", "more": "Tìm hiểu thêm về CalDAV trên Vikunja", - "tokens": "CalDAV Tokens", - "tokensHowTo": "Bạn có thể sử dụng mã token CalDAV thay cho mật khẩu để đăng nhập ở bước vừa rồi.", - "createToken": "Tạo mã token", - "tokenCreated": "Đây là mã token của bạn: {token}", - "wontSeeItAgain": "Ghi lại, bạn sẽ không thể thấy nó thêm lần nữa.", - "mustUseToken": "Bạn cần tạo mã token CalDAV nếu như muốn sử dụng CalDAV với dịch vụ bên thứ ba. Sử dụng mã token như mật khẩu.", - "usernameIs": "Tên đăng nhập của bạn là: {0}" + "tokens": "CalDAV Tokens" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/zh-CN.json b/frontend/src/i18n/lang/zh-CN.json index 021444816..57dd2c69f 100644 --- a/frontend/src/i18n/lang/zh-CN.json +++ b/frontend/src/i18n/lang/zh-CN.json @@ -124,15 +124,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "您可以将 Vikunja 连接到 CalDAV 客户端来查看和管理来自不同客户端的所有任务。请将此URL输入您的客户端:", "more": "更多关于 Vikunja 的 CalDAV 信息", - "tokens": "CalDAV Tokens", - "tokensHowTo": "您可以使用 CalDAV Tokens 代替密码登录上述端点。", - "createToken": "创建令牌", - "tokenCreated": "这是您的令牌: {token}", - "wontSeeItAgain": "将其写下,您将无法再次看到。", - "mustUseToken": "如果您想要通过第三方客户端来登录使用 CalDAV,则需要创建一个 CalDAV 令牌,并使用该令牌作为密码。", - "usernameIs": "您的用户名是: {0}" + "tokens": "CalDAV Tokens" }, "avatar": { "title": "头像", diff --git a/frontend/src/i18n/lang/zh-TW.json b/frontend/src/i18n/lang/zh-TW.json index a2c659bef..44b5074ee 100644 --- a/frontend/src/i18n/lang/zh-TW.json +++ b/frontend/src/i18n/lang/zh-TW.json @@ -144,15 +144,8 @@ }, "caldav": { "title": "CalDAV", - "howTo": "您可以將 Vikunja 連接到 CalDAV 用戶端,從不同用戶端查看並管理所有任務。請在您的用戶端中輸入此 URL:", "more": "更多關於 Vikunja 的 CalDAV 信息", - "tokens": "CalDAV 令牌", - "tokensHowTo": "您可以使用 CalDAV Tokens 代替密碼登錄上述端點。", - "createToken": "建立令牌", - "tokenCreated": "這是您的令牌: {token}", - "wontSeeItAgain": "請將它記下,之後將無法再次查看。", - "mustUseToken": "如果您想要通過第三方客户端來登錄使用 CalDAV,則需要創建一個 CalDAV 令牌,並使用該令牌作為密碼。", - "usernameIs": "您的用户名是: {0}" + "tokens": "CalDAV 令牌" }, "avatar": { "title": "頭像", From 39e16653aaa4aebcef76d11002b3b832c68bb7d2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:31:27 +0100 Subject: [PATCH 002/501] fix: add ORDER BY to ListUsers query for deterministic ordering The query had no ORDER BY clause, causing non-deterministic result ordering on PostgreSQL where row order is not guaranteed. --- pkg/user/users_project.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/user/users_project.go b/pkg/user/users_project.go index 1ac92dfb5..4ce88ed49 100644 --- a/pkg/user/users_project.go +++ b/pkg/user/users_project.go @@ -149,6 +149,7 @@ func ListUsers(s *xorm.Session, search string, currentUser *User, opts *ProjectU err = s. Where(cond). + OrderBy("id"). Find(&users) outer: From 7a258f67c7bc248ea2a8573553cef023b9bd3468 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:31:37 +0100 Subject: [PATCH 003/501] refactor: extract shared RefreshSession helper The cookie-based /user/token/refresh handler had session refresh logic (lookup, expiry check, token rotation, user fetch, JWT generation) that will be reused by the OAuth token endpoint. Extract it into auth.RefreshSession() and rewrite RefreshToken to use it. --- pkg/modules/auth/auth.go | 92 ++++++++++++++++++++++++++++++++++++++ pkg/routes/api/v1/login.go | 80 +++------------------------------ 2 files changed, 98 insertions(+), 74 deletions(-) diff --git a/pkg/modules/auth/auth.go b/pkg/modules/auth/auth.go index 476416695..dc2ba2c78 100644 --- a/pkg/modules/auth/auth.go +++ b/pkg/modules/auth/auth.go @@ -216,3 +216,95 @@ func CreateUserWithRandomUsername(s *xorm.Session, uu *user.User) (u *user.User, err = models.CreateNewProjectForUser(s, u) return } + +// RefreshResult holds the result of a successful session refresh. +type RefreshResult struct { + AccessToken string + NewRefreshToken string + ExpiresIn int64 + IsLongSession bool + SessionID string +} + +// RefreshSession looks up a session by its raw refresh token, validates it, +// rotates the refresh token, fetches the user, and generates a new JWT. +// It handles its own DB session (open/commit/rollback). +// +// On user status errors (disabled/locked), the session is deleted before +// returning the error so the caller can handle cleanup (e.g. clearing cookies). +func RefreshSession(rawRefreshToken string) (*RefreshResult, error) { + s := db.NewSession() + defer s.Close() + + session, err := models.GetSessionByRefreshToken(s, rawRefreshToken) + if err != nil { + _ = s.Rollback() + if models.IsErrSessionNotFound(err) { + return nil, echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired refresh token.") + } + return nil, err + } + + maxAge := time.Duration(config.ServiceJWTTTL.GetInt64()) * time.Second + if session.IsLongSession { + maxAge = time.Duration(config.ServiceJWTTTLLong.GetInt64()) * time.Second + } + if time.Since(session.LastActive) > maxAge { + if _, err := s.Where("id = ?", session.ID).Delete(&models.Session{}); err != nil { + _ = s.Rollback() + return nil, err + } + if err := s.Commit(); err != nil { + return nil, err + } + return nil, echo.NewHTTPError(http.StatusUnauthorized, "Session expired.") + } + + if err := models.UpdateSessionLastActive(s, session.ID); err != nil { + _ = s.Rollback() + return nil, err + } + + newRawToken, err := models.RotateRefreshToken(s, session) + if err != nil { + _ = s.Rollback() + if models.IsErrSessionNotFound(err) { + return nil, echo.NewHTTPError(http.StatusUnauthorized, "Refresh token already used.") + } + return nil, err + } + + u, err := user.GetUserByID(s, session.UserID) + if err != nil { + if user.IsErrUserStatusError(err) { + if _, delErr := s.Where("id = ?", session.ID).Delete(&models.Session{}); delErr != nil { + _ = s.Rollback() + return nil, delErr + } + if commitErr := s.Commit(); commitErr != nil { + return nil, commitErr + } + return nil, err + } + _ = s.Rollback() + return nil, err + } + + accessToken, err := NewUserJWTAuthtoken(u, session.ID) + if err != nil { + _ = s.Rollback() + return nil, err + } + + if err := s.Commit(); err != nil { + return nil, err + } + + return &RefreshResult{ + AccessToken: accessToken, + NewRefreshToken: newRawToken, + ExpiresIn: config.ServiceJWTTTLShort.GetInt64(), + IsLongSession: session.IsLongSession, + SessionID: session.ID, + }, nil +} diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index cf27244e5..7856534b9 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -18,7 +18,6 @@ package v1 import ( "net/http" - "time" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" @@ -189,90 +188,23 @@ func RefreshToken(c *echo.Context) (err error) { if err != nil || cookie.Value == "" { return echo.NewHTTPError(http.StatusUnauthorized, "No refresh token provided.") } - rawToken := cookie.Value - s := db.NewSession() - defer s.Close() - - session, err := models.GetSessionByRefreshToken(s, rawToken) + result, err := auth.RefreshSession(cookie.Value) if err != nil { - _ = s.Rollback() - if models.IsErrSessionNotFound(err) { - // Don't clear the cookie here — another tab may have already - // rotated the token, and clearing would overwrite the new cookie. - return echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired refresh token.") + if user2.IsErrUserStatusError(err) { + auth.ClearRefreshTokenCookie(c) } return err } - // Check if the session has expired based on its type - maxAge := time.Duration(config.ServiceJWTTTL.GetInt64()) * time.Second - if session.IsLongSession { - maxAge = time.Duration(config.ServiceJWTTTLLong.GetInt64()) * time.Second - } - if time.Since(session.LastActive) > maxAge { - if _, err := s.Where("id = ?", session.ID).Delete(&models.Session{}); err != nil { - _ = s.Rollback() - return err - } - if err := s.Commit(); err != nil { - return err - } - auth.ClearRefreshTokenCookie(c) - return echo.NewHTTPError(http.StatusUnauthorized, "Session expired.") - } - - if err := models.UpdateSessionLastActive(s, session.ID); err != nil { - _ = s.Rollback() - return err - } - - newRawToken, err := models.RotateRefreshToken(s, session) - if err != nil { - _ = s.Rollback() - if models.IsErrSessionNotFound(err) { - // Don't clear the cookie — a concurrent request in another tab - // may have already rotated the token successfully. - return echo.NewHTTPError(http.StatusUnauthorized, "Refresh token already used.") - } - return err - } - - u, err := user2.GetUserWithEmail(s, &user2.User{ID: session.UserID}) - if user2.IsErrUserStatusError(err) { - if _, delErr := s.Where("id = ?", session.ID).Delete(&models.Session{}); delErr != nil { - _ = s.Rollback() - return delErr - } - if commitErr := s.Commit(); commitErr != nil { - return commitErr - } - auth.ClearRefreshTokenCookie(c) - return err - } - if err != nil { - _ = s.Rollback() - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() - return err - } - - t, err := auth.NewUserJWTAuthtoken(u, session.ID) - if err != nil { - return err - } - cookieMaxAge := int(config.ServiceJWTTTL.GetInt64()) - if session.IsLongSession { + if result.IsLongSession { cookieMaxAge = int(config.ServiceJWTTTLLong.GetInt64()) } - auth.SetRefreshTokenCookie(c, newRawToken, cookieMaxAge) + auth.SetRefreshTokenCookie(c, result.NewRefreshToken, cookieMaxAge) c.Response().Header().Set("Cache-Control", "no-store") - return c.JSON(http.StatusOK, auth.Token{Token: t}) + return c.JSON(http.StatusOK, auth.Token{Token: result.AccessToken}) } // Logout deletes the current session from the server. From 71282dcffdbd2e68c1a261208924e3a41230557b Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:31:47 +0100 Subject: [PATCH 004/501] feat: add OAuth 2.0 authorization code model and migration Add the OAuthCode model for storing short-lived authorization codes with PKCE challenges. Codes are hashed (SHA-256) before storage and are single-use with a 10-minute expiry. Add the database migration and OAuth-specific error types. --- pkg/db/fixtures/oauth_codes.yml | 2 + pkg/migration/20260226172819.go | 53 ++++++++++ pkg/models/error.go | 179 ++++++++++++++++++++++++++++++++ pkg/models/models.go | 1 + pkg/models/oauth_codes.go | 99 ++++++++++++++++++ pkg/models/setup_tests.go | 1 + 6 files changed, 335 insertions(+) create mode 100644 pkg/db/fixtures/oauth_codes.yml create mode 100644 pkg/migration/20260226172819.go create mode 100644 pkg/models/oauth_codes.go diff --git a/pkg/db/fixtures/oauth_codes.yml b/pkg/db/fixtures/oauth_codes.yml new file mode 100644 index 000000000..7dd438752 --- /dev/null +++ b/pkg/db/fixtures/oauth_codes.yml @@ -0,0 +1,2 @@ +[] + diff --git a/pkg/migration/20260226172819.go b/pkg/migration/20260226172819.go new file mode 100644 index 000000000..2ca9f740c --- /dev/null +++ b/pkg/migration/20260226172819.go @@ -0,0 +1,53 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migration + +import ( + "time" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type oauthCodes20260226172819 struct { + ID int64 `xorm:"autoincr not null unique pk"` + UserID int64 `xorm:"bigint not null"` + Code string `xorm:"varchar(128) not null unique index"` + ExpiresAt time.Time `xorm:"not null"` + ClientID string `xorm:"varchar(255) not null"` + RedirectURI string `xorm:"text not null"` + CodeChallenge string `xorm:"varchar(128) not null"` + CodeChallengeMethod string `xorm:"varchar(10) not null"` + Created time.Time `xorm:"created not null"` +} + +func (oauthCodes20260226172819) TableName() string { + return "oauth_codes" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20260226172819", + Description: "add oauth_codes table for OAuth 2.0 authorization codes", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync(oauthCodes20260226172819{}) + }, + Rollback: func(tx *xorm.Engine) error { + return tx.DropTables(oauthCodes20260226172819{}) + }, + }) +} diff --git a/pkg/models/error.go b/pkg/models/error.go index 89179e440..756f2ca93 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -2184,3 +2184,182 @@ func (err *ErrSessionNotFound) HTTPError() web.HTTPError { Message: "The session does not exist.", } } + +// ==================== +// OAuth Server Errors +// ==================== + +// ErrOAuthClientNotFound represents an error where the OAuth client ID is not recognized. +type ErrOAuthClientNotFound struct{} + +// IsErrOAuthClientNotFound checks if an error is ErrOAuthClientNotFound. +func IsErrOAuthClientNotFound(err error) bool { + _, ok := err.(*ErrOAuthClientNotFound) + return ok +} + +func (err *ErrOAuthClientNotFound) Error() string { + return "OAuth client not found" +} + +// ErrCodeOAuthClientNotFound holds the unique world-error code of this error +const ErrCodeOAuthClientNotFound = 17001 + +// HTTPError holds the http error description +func (err *ErrOAuthClientNotFound) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthClientNotFound, + Message: "The OAuth client ID is not recognized.", + } +} + +// ErrOAuthInvalidRedirectURI represents an error where the redirect URI is not allowed. +type ErrOAuthInvalidRedirectURI struct{} + +// IsErrOAuthInvalidRedirectURI checks if an error is ErrOAuthInvalidRedirectURI. +func IsErrOAuthInvalidRedirectURI(err error) bool { + _, ok := err.(*ErrOAuthInvalidRedirectURI) + return ok +} + +func (err *ErrOAuthInvalidRedirectURI) Error() string { + return "Invalid redirect URI" +} + +// ErrCodeOAuthInvalidRedirectURI holds the unique world-error code of this error +const ErrCodeOAuthInvalidRedirectURI = 17002 + +// HTTPError holds the http error description +func (err *ErrOAuthInvalidRedirectURI) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthInvalidRedirectURI, + Message: "The redirect URI is not allowed for this client.", + } +} + +// ErrOAuthMissingPKCE represents an error where PKCE parameters are missing or invalid. +type ErrOAuthMissingPKCE struct{} + +// IsErrOAuthMissingPKCE checks if an error is ErrOAuthMissingPKCE. +func IsErrOAuthMissingPKCE(err error) bool { + _, ok := err.(*ErrOAuthMissingPKCE) + return ok +} + +func (err *ErrOAuthMissingPKCE) Error() string { + return "PKCE is required" +} + +// ErrCodeOAuthMissingPKCE holds the unique world-error code of this error +const ErrCodeOAuthMissingPKCE = 17003 + +// HTTPError holds the http error description +func (err *ErrOAuthMissingPKCE) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthMissingPKCE, + Message: "PKCE (code_challenge with S256 method) is required.", + } +} + +// ErrOAuthCodeInvalid represents an error where the authorization code is invalid or already used. +type ErrOAuthCodeInvalid struct{} + +// IsErrOAuthCodeInvalid checks if an error is ErrOAuthCodeInvalid. +func IsErrOAuthCodeInvalid(err error) bool { + _, ok := err.(*ErrOAuthCodeInvalid) + return ok +} + +func (err *ErrOAuthCodeInvalid) Error() string { + return "Invalid authorization code" +} + +// ErrCodeOAuthCodeInvalid holds the unique world-error code of this error +const ErrCodeOAuthCodeInvalid = 17004 + +// HTTPError holds the http error description +func (err *ErrOAuthCodeInvalid) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthCodeInvalid, + Message: "The authorization code is invalid or has already been used.", + } +} + +// ErrOAuthCodeExpired represents an error where the authorization code has expired. +type ErrOAuthCodeExpired struct{} + +// IsErrOAuthCodeExpired checks if an error is ErrOAuthCodeExpired. +func IsErrOAuthCodeExpired(err error) bool { + _, ok := err.(*ErrOAuthCodeExpired) + return ok +} + +func (err *ErrOAuthCodeExpired) Error() string { + return "Authorization code expired" +} + +// ErrCodeOAuthCodeExpired holds the unique world-error code of this error +const ErrCodeOAuthCodeExpired = 17005 + +// HTTPError holds the http error description +func (err *ErrOAuthCodeExpired) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthCodeExpired, + Message: "The authorization code has expired.", + } +} + +// ErrOAuthPKCEVerifyFailed represents an error where the PKCE code_verifier does not match. +type ErrOAuthPKCEVerifyFailed struct{} + +// IsErrOAuthPKCEVerifyFailed checks if an error is ErrOAuthPKCEVerifyFailed. +func IsErrOAuthPKCEVerifyFailed(err error) bool { + _, ok := err.(*ErrOAuthPKCEVerifyFailed) + return ok +} + +func (err *ErrOAuthPKCEVerifyFailed) Error() string { + return "PKCE verification failed" +} + +// ErrCodeOAuthPKCEVerifyFailed holds the unique world-error code of this error +const ErrCodeOAuthPKCEVerifyFailed = 17006 + +// HTTPError holds the http error description +func (err *ErrOAuthPKCEVerifyFailed) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthPKCEVerifyFailed, + Message: "The code_verifier does not match the code_challenge.", + } +} + +// ErrOAuthInvalidGrantType represents an error where the grant_type is not supported. +type ErrOAuthInvalidGrantType struct{} + +// IsErrOAuthInvalidGrantType checks if an error is ErrOAuthInvalidGrantType. +func IsErrOAuthInvalidGrantType(err error) bool { + _, ok := err.(*ErrOAuthInvalidGrantType) + return ok +} + +func (err *ErrOAuthInvalidGrantType) Error() string { + return "Invalid grant type" +} + +// ErrCodeOAuthInvalidGrantType holds the unique world-error code of this error +const ErrCodeOAuthInvalidGrantType = 17007 + +// HTTPError holds the http error description +func (err *ErrOAuthInvalidGrantType) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeOAuthInvalidGrantType, + Message: "The grant_type is not supported. Use 'authorization_code' or 'refresh_token'.", + } +} diff --git a/pkg/models/models.go b/pkg/models/models.go index 906038138..df562ef90 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -70,6 +70,7 @@ func GetTables() []interface{} { &TaskBucket{}, &TaskUnreadStatus{}, &Session{}, + &OAuthCode{}, } } diff --git a/pkg/models/oauth_codes.go b/pkg/models/oauth_codes.go new file mode 100644 index 000000000..b201d7721 --- /dev/null +++ b/pkg/models/oauth_codes.go @@ -0,0 +1,99 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "time" + + "code.vikunja.io/api/pkg/utils" + + "xorm.io/xorm" +) + +// OAuthCode represents a short-lived OAuth 2.0 authorization code. +type OAuthCode struct { + ID int64 `xorm:"autoincr not null unique pk" json:"id"` + UserID int64 `xorm:"bigint not null" json:"-"` + Code string `xorm:"varchar(128) not null unique index" json:"-"` + ExpiresAt time.Time `xorm:"not null" json:"-"` + ClientID string `xorm:"varchar(255) not null" json:"-"` + RedirectURI string `xorm:"text not null" json:"-"` + CodeChallenge string `xorm:"varchar(128) not null" json:"-"` + CodeChallengeMethod string `xorm:"varchar(10) not null" json:"-"` + Created time.Time `xorm:"created not null" json:"created"` +} + +func (*OAuthCode) TableName() string { + return "oauth_codes" +} + +// CreateOAuthCode generates a cryptographically random authorization code, +// stores it, and returns the code string. +func CreateOAuthCode(s *xorm.Session, userID int64, clientID, redirectURI, codeChallenge, codeChallengeMethod string) (code string, err error) { + rawCode, err := utils.CryptoRandomString(64) + if err != nil { + return "", err + } + + oauthCode := &OAuthCode{ + UserID: userID, + Code: HashSessionToken(rawCode), + ExpiresAt: time.Now().Add(10 * time.Minute), + ClientID: clientID, + RedirectURI: redirectURI, + CodeChallenge: codeChallenge, + CodeChallengeMethod: codeChallengeMethod, + } + + _, err = s.Insert(oauthCode) + if err != nil { + return "", err + } + + return rawCode, nil +} + +// GetAndDeleteOAuthCode looks up an authorization code and deletes it (single-use). +// Returns the code record or an error if not found or expired. +func GetAndDeleteOAuthCode(s *xorm.Session, code string) (*OAuthCode, error) { + oauthCode := &OAuthCode{} + has, err := s.Where("code = ?", HashSessionToken(code)).Get(oauthCode) + if err != nil { + return nil, err + } + if !has { + return nil, &ErrOAuthCodeInvalid{} + } + + // Delete immediately (single-use). + // Check affected rows to prevent a race where two concurrent requests + // both read the same code before either deletes it. + affected, err := s.Where("id = ?", oauthCode.ID).Delete(&OAuthCode{}) + if err != nil { + return nil, err + } + if affected == 0 { + return nil, &ErrOAuthCodeInvalid{} + } + + // Check expiry after deletion to prevent reuse of expired codes + if time.Now().After(oauthCode.ExpiresAt) { + return nil, &ErrOAuthCodeExpired{} + } + + return oauthCode, nil +} diff --git a/pkg/models/setup_tests.go b/pkg/models/setup_tests.go index 8605ea06c..90b31927a 100644 --- a/pkg/models/setup_tests.go +++ b/pkg/models/setup_tests.go @@ -77,6 +77,7 @@ func SetupTests() { "sessions", "webhooks", "totp", + "oauth_codes", ) if err != nil { log.Fatal(err) From a6e74751539f5a9f0fa2d82a2d912009dbfe2d42 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:31:54 +0100 Subject: [PATCH 005/501] feat: add OAuth client validation and PKCE verification Add redirect URI validation that allowlists vikunja-* custom protocol schemes, rejecting http/https and dangerous schemes like javascript:. Add PKCE S256 verification following RFC 7636. --- pkg/modules/auth/oauth2server/client.go | 35 +++++++++++++++++++++++++ pkg/modules/auth/oauth2server/pkce.go | 34 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 pkg/modules/auth/oauth2server/client.go create mode 100644 pkg/modules/auth/oauth2server/pkce.go diff --git a/pkg/modules/auth/oauth2server/client.go b/pkg/modules/auth/oauth2server/client.go new file mode 100644 index 000000000..44e381958 --- /dev/null +++ b/pkg/modules/auth/oauth2server/client.go @@ -0,0 +1,35 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "net/url" + "strings" +) + +// ValidateRedirectURI checks that the redirect_uri uses a scheme starting with +// "vikunja-". This allowlists only Vikunja native app schemes (e.g. +// vikunja-flutter://callback) and rejects dangerous schemes like javascript:, +// data:, http:, https:, etc. +func ValidateRedirectURI(redirectURI string) bool { + u, err := url.Parse(redirectURI) + if err != nil || u.Scheme == "" { + return false + } + + return strings.HasPrefix(u.Scheme, "vikunja-") +} diff --git a/pkg/modules/auth/oauth2server/pkce.go b/pkg/modules/auth/oauth2server/pkce.go new file mode 100644 index 000000000..6f88b0fb1 --- /dev/null +++ b/pkg/modules/auth/oauth2server/pkce.go @@ -0,0 +1,34 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "crypto/sha256" + "encoding/base64" +) + +// VerifyPKCE verifies a PKCE code_verifier against a stored code_challenge using S256. +// S256: BASE64URL(SHA256(code_verifier)) == code_challenge +func VerifyPKCE(codeVerifier, codeChallenge, codeChallengeMethod string) bool { + if codeChallengeMethod != "S256" { + return false + } + + h := sha256.Sum256([]byte(codeVerifier)) + computed := base64.RawURLEncoding.EncodeToString(h[:]) + return computed == codeChallenge +} From 8b379b7466ea6a3cb2f9d91b28c16d450f6a5987 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:01 +0100 Subject: [PATCH 006/501] feat: add OAuth 2.0 authorize endpoint Add POST /api/v1/oauth/authorize behind auth middleware. Validates OAuth parameters (response_type, redirect_uri, PKCE), fetches the authenticated user, creates an authorization code, and returns it as JSON for the frontend to handle the redirect. --- pkg/modules/auth/oauth2server/authorize.go | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 pkg/modules/auth/oauth2server/authorize.go diff --git a/pkg/modules/auth/oauth2server/authorize.go b/pkg/modules/auth/oauth2server/authorize.go new file mode 100644 index 000000000..873c00900 --- /dev/null +++ b/pkg/modules/auth/oauth2server/authorize.go @@ -0,0 +1,100 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" + + "github.com/labstack/echo/v5" +) + +// authorizeRequest represents the JSON body for the authorize endpoint. +type authorizeRequest struct { + ResponseType string `json:"response_type"` + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + State string `json:"state"` + CodeChallenge string `json:"code_challenge"` + CodeChallengeMethod string `json:"code_challenge_method"` +} + +// AuthorizeResponse is returned on successful authorization code creation. +type AuthorizeResponse struct { + Code string `json:"code"` + RedirectURI string `json:"redirect_uri"` + State string `json:"state"` +} + +// HandleAuthorize handles POST /oauth/authorize. +// It validates the OAuth parameters, creates an authorization code, and +// returns it as JSON. Authentication is handled by the token middleware. +func HandleAuthorize(c *echo.Context) error { + var req authorizeRequest + if err := c.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body") + } + + // Validate response_type + if req.ResponseType != "code" { + return echo.NewHTTPError(http.StatusBadRequest, "response_type must be 'code'") + } + + // Validate redirect_uri + if !ValidateRedirectURI(req.RedirectURI) { + return &models.ErrOAuthInvalidRedirectURI{} + } + + // Validate PKCE (required) + if req.CodeChallenge == "" || req.CodeChallengeMethod != "S256" { + return &models.ErrOAuthMissingPKCE{} + } + + // Get the authenticated user from the middleware + u, err := user.GetCurrentUser(c) + if err != nil { + return err + } + + s := db.NewSession() + defer s.Close() + + fullUser, err := user.GetUserByID(s, u.ID) + if err != nil { + _ = s.Rollback() + return err + } + + code, err := models.CreateOAuthCode(s, fullUser.ID, req.ClientID, req.RedirectURI, req.CodeChallenge, req.CodeChallengeMethod) + if err != nil { + _ = s.Rollback() + return err + } + + if err := s.Commit(); err != nil { + return err + } + + return c.JSON(http.StatusOK, AuthorizeResponse{ + Code: code, + RedirectURI: req.RedirectURI, + State: req.State, + }) +} From 7827ff64b9e419b3d6febc840937b7141f21b909 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:10 +0100 Subject: [PATCH 007/501] feat: add OAuth 2.0 token endpoint Add POST /api/v1/oauth/token supporting authorization_code and refresh_token grant types. Validates PKCE, exchanges codes for JWT access tokens with refresh token rotation. Uses the shared RefreshSession helper for the refresh grant. --- pkg/modules/auth/oauth2server/token.go | 144 +++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 pkg/modules/auth/oauth2server/token.go diff --git a/pkg/modules/auth/oauth2server/token.go b/pkg/modules/auth/oauth2server/token.go new file mode 100644 index 000000000..2725b988d --- /dev/null +++ b/pkg/modules/auth/oauth2server/token.go @@ -0,0 +1,144 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/user" + + "github.com/labstack/echo/v5" +) + +// TokenResponse is the OAuth 2.0 token response. +type TokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` +} + +// tokenRequest holds the JSON body of a POST /oauth/token request. +type tokenRequest struct { + GrantType string `json:"grant_type"` + Code string `json:"code"` + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + CodeVerifier string `json:"code_verifier"` + RefreshToken string `json:"refresh_token"` +} + +// HandleToken handles POST /oauth/token. +// Supports grant_type=authorization_code and grant_type=refresh_token. +func HandleToken(c *echo.Context) error { + var req tokenRequest + if err := c.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body") + } + + switch req.GrantType { + case "authorization_code": + return handleAuthorizationCodeGrant(c, &req) + case "refresh_token": + return handleRefreshTokenGrant(c, &req) + default: + return &models.ErrOAuthInvalidGrantType{} + } +} + +func handleAuthorizationCodeGrant(c *echo.Context, req *tokenRequest) error { + s := db.NewSession() + defer s.Close() + + // Look up and delete the authorization code (single-use) + oauthCode, err := models.GetAndDeleteOAuthCode(s, req.Code) + if err != nil { + _ = s.Rollback() + return err + } + + // Validate client_id matches + if oauthCode.ClientID != req.ClientID { + _ = s.Rollback() + return &models.ErrOAuthClientNotFound{} + } + + // Validate redirect_uri matches + if oauthCode.RedirectURI != req.RedirectURI { + _ = s.Rollback() + return &models.ErrOAuthInvalidRedirectURI{} + } + + // Verify PKCE + if !VerifyPKCE(req.CodeVerifier, oauthCode.CodeChallenge, oauthCode.CodeChallengeMethod) { + _ = s.Rollback() + return &models.ErrOAuthPKCEVerifyFailed{} + } + + // Create a session (reuses existing session infrastructure) + deviceInfo := c.Request().UserAgent() + ipAddress := c.RealIP() + session, err := models.CreateSession(s, oauthCode.UserID, deviceInfo, ipAddress, false) + if err != nil { + _ = s.Rollback() + return err + } + + u, err := user.GetUserByID(s, oauthCode.UserID) + if err != nil { + _ = s.Rollback() + return err + } + + // Generate JWT + accessToken, err := auth.NewUserJWTAuthtoken(u, session.ID) + if err != nil { + _ = s.Rollback() + return err + } + + if err := s.Commit(); err != nil { + return err + } + + c.Response().Header().Set("Cache-Control", "no-store") + return c.JSON(http.StatusOK, TokenResponse{ + AccessToken: accessToken, + TokenType: "bearer", + ExpiresIn: config.ServiceJWTTTLShort.GetInt64(), + RefreshToken: session.RefreshToken, + }) +} + +func handleRefreshTokenGrant(c *echo.Context, req *tokenRequest) error { + result, err := auth.RefreshSession(req.RefreshToken) + if err != nil { + return err + } + + c.Response().Header().Set("Cache-Control", "no-store") + return c.JSON(http.StatusOK, TokenResponse{ + AccessToken: result.AccessToken, + TokenType: "bearer", + ExpiresIn: result.ExpiresIn, + RefreshToken: result.NewRefreshToken, + }) +} From e5987acf806f5eb32a638d1c27af9c0e0d89c592 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:16 +0100 Subject: [PATCH 008/501] feat: register OAuth authorize and token routes Add POST /api/v1/oauth/authorize (authenticated) and POST /api/v1/oauth/token (unauthenticated) routes. --- pkg/routes/routes.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 02c2736f7..2e8e1ebbf 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -61,6 +61,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth/oauth2server" "code.vikunja.io/api/pkg/modules/auth/openid" "code.vikunja.io/api/pkg/modules/background" backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler" @@ -306,6 +307,7 @@ var unauthenticatedAPIPaths = map[string]bool{ "/api/v1/docs.json": true, "/api/v1/docs": true, "/api/v1/metrics": true, + "/api/v1/oauth/token": true, } // collectRoutesForAPITokens collects all routes for API token permission checking. @@ -379,6 +381,10 @@ func registerAPIRoutes(a *echo.Group) { ur.POST("/auth/openid/:provider/callback", openid.HandleCallback) } + // OAuth 2.0 token endpoint — unauthenticated because it validates + // credentials (authorization code or refresh token) itself. + ur.POST("/oauth/token", oauth2server.HandleToken) + // Testing if config.ServiceTestingtoken.GetString() != "" { n.PATCH("/test/:table", apiv1.HandleTesting) @@ -405,6 +411,9 @@ func registerAPIRoutes(a *echo.Group) { a.POST("/token/test", apiv1.CheckToken) a.GET("/routes", models.GetAvailableAPIRoutesForToken) + // OAuth 2.0 authorize endpoint — requires authentication. + a.POST("/oauth/authorize", oauth2server.HandleAuthorize) + // Avatar endpoint a.GET("/avatar/:username", apiv1.GetAvatar) From 0471f8a7291c7f7f65e4dfa560573f3dc56997de Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:23 +0100 Subject: [PATCH 009/501] feat: add frontend OAuth authorize route and component Add /oauth/authorize frontend route with OAuthAuthorize.vue that handles the OAuth authorization flow: validates required query params, calls the API to generate an authorization code, and redirects to the callback URI. Authentication is handled by the standard router guard. --- frontend/src/i18n/lang/en.json | 1 + frontend/src/router/index.ts | 5 ++ frontend/src/views/user/Login.vue | 1 + frontend/src/views/user/OAuthAuthorize.vue | 77 ++++++++++++++++++++++ frontend/src/views/user/OpenIdAuth.vue | 3 +- 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 frontend/src/views/user/OAuthAuthorize.vue diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 699c01094..28e185a4a 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -54,6 +54,7 @@ "authenticating": "Authenticating…", "openIdStateError": "State does not match, refusing to continue!", "openIdGeneralError": "An error occurred while authenticating against the third party.", + "oauthMissingParams": "Missing required OAuth parameters: {params}", "logout": "Logout", "emailInvalid": "Please enter a valid email address.", "usernameRequired": "Please provide a username.", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index c19b4642d..1f4b15121 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -394,6 +394,11 @@ const router = createRouter({ name: 'openid.auth', component: OpenIdAuth, }, + { + path: '/oauth/authorize', + name: 'oauth.authorize', + component: () => import('@/views/user/OAuthAuthorize.vue'), + }, { path: '/about', name: 'about', diff --git a/frontend/src/views/user/Login.vue b/frontend/src/views/user/Login.vue index 9eda9088a..29ee5c90f 100644 --- a/frontend/src/views/user/Login.vue +++ b/frontend/src/views/user/Login.vue @@ -217,6 +217,7 @@ async function submit() { try { await authStore.login(credentials) authStore.setNeedsTotpPasscode(false) + redirectIfSaved() } catch (e) { if (e.response?.data.code === 1017 && !credentials.totpPasscode) { diff --git a/frontend/src/views/user/OAuthAuthorize.vue b/frontend/src/views/user/OAuthAuthorize.vue new file mode 100644 index 000000000..49318d55f --- /dev/null +++ b/frontend/src/views/user/OAuthAuthorize.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/src/views/user/OpenIdAuth.vue b/frontend/src/views/user/OpenIdAuth.vue index 33268c80b..e581aaee2 100644 --- a/frontend/src/views/user/OpenIdAuth.vue +++ b/frontend/src/views/user/OpenIdAuth.vue @@ -80,8 +80,9 @@ async function authenticateWithCode() { provider: route.params.provider, code: route.query.code, }) + redirectIfSaved() - } catch(e) { + } catch (e) { errorMessage.value = getErrorText(e) } finally { localStorage.removeItem('authenticating') From 649043aceb0efaf5575327b26829260a83087dfc Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:40 +0100 Subject: [PATCH 010/501] test: add tests for OAuth 2.0 authorization flow Add web tests covering the authorize endpoint, token exchange, PKCE verification, single-use codes, and refresh token rotation. Add unit tests for redirect URI validation and PKCE. Add E2E test for the full browser-based authorization code flow with login redirect. Extract setupApiUrl helper for E2E tests to avoid duplication. --- .../tests/e2e/user/oauth-authorize.spec.ts | 80 +++++ .../tests/e2e/user/session-refresh.spec.ts | 8 +- frontend/tests/support/authenticateUser.ts | 24 +- pkg/modules/auth/oauth2server/client_test.go | 50 +++ pkg/modules/auth/oauth2server/pkce_test.go | 51 +++ pkg/webtests/oauth2_test.go | 308 ++++++++++++++++++ 6 files changed, 507 insertions(+), 14 deletions(-) create mode 100644 frontend/tests/e2e/user/oauth-authorize.spec.ts create mode 100644 pkg/modules/auth/oauth2server/client_test.go create mode 100644 pkg/modules/auth/oauth2server/pkce_test.go create mode 100644 pkg/webtests/oauth2_test.go diff --git a/frontend/tests/e2e/user/oauth-authorize.spec.ts b/frontend/tests/e2e/user/oauth-authorize.spec.ts new file mode 100644 index 000000000..908e9ae3f --- /dev/null +++ b/frontend/tests/e2e/user/oauth-authorize.spec.ts @@ -0,0 +1,80 @@ +import {createHash, randomBytes} from 'crypto' +import {test, expect} from '../../support/fixtures' +import {UserFactory} from '../../factories/user' +import {setupApiUrl} from '../../support/authenticateUser' +import {TEST_PASSWORD} from '../../support/constants' + +test.describe('OAuth 2.0 Authorization Flow', () => { + let username: string + + test.beforeEach(async ({apiContext}) => { + const [user] = await UserFactory.create(1) + username = user.username + }) + + test('Full browser authorization code flow with PKCE', async ({page, apiContext}) => { + await setupApiUrl(page) + + // Generate PKCE code_verifier and code_challenge (S256) + const codeVerifier = randomBytes(32).toString('base64url') + const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url') + const state = randomBytes(16).toString('base64url') + + // Build the authorize URL as a frontend route with OAuth query params. + // The OAuthAuthorize.vue component reads these and POSTs to the API. + const authorizeParams = new URLSearchParams({ + response_type: 'code', + client_id: 'vikunja', + redirect_uri: 'vikunja-flutter://callback', + code_challenge: codeChallenge, + code_challenge_method: 'S256', + state, + }) + + // Navigate to the OAuth authorize frontend route. + // The user is not logged in, so the router guard saves the route + // and redirects to /login. + await page.goto(`/oauth/authorize?${authorizeParams}`) + await expect(page).toHaveURL(/\/login/) + + // Register the response listener BEFORE clicking Login, because after + // login redirectIfSaved() navigates back to /oauth/authorize and the + // component immediately POSTs to the API. + const authorizeResponsePromise = page.waitForResponse( + response => response.url().includes('/api/v1/oauth/authorize') && response.request().method() === 'POST', + {timeout: 15000}, + ) + + // Log in via the browser UI + await page.locator('input[id=username]').fill(username) + await page.locator('input[id=password]').fill(TEST_PASSWORD) + await page.locator('.button').filter({hasText: 'Login'}).click() + + // Wait for the authorize API call that fires after login redirect + const authorizeResponse = await authorizeResponsePromise + const authorizeBody = await authorizeResponse.json() + expect(authorizeBody.code).toBeTruthy() + expect(authorizeBody.redirect_uri).toBe('vikunja-flutter://callback') + expect(authorizeBody.state).toBe(state) + + const code = authorizeBody.code + + // Exchange the authorization code for tokens + const tokenResponse = await apiContext.post('oauth/token', { + data: { + grant_type: 'authorization_code', + code, + client_id: 'vikunja', + redirect_uri: 'vikunja-flutter://callback', + code_verifier: codeVerifier, + }, + }) + + expect(tokenResponse.ok()).toBe(true) + const tokenBody = await tokenResponse.json() + expect(tokenBody.access_token).toBeTruthy() + expect(tokenBody.refresh_token).toBeTruthy() + expect(tokenBody.token_type).toBe('bearer') + expect(tokenBody.expires_in).toBeGreaterThan(0) + }) +}) diff --git a/frontend/tests/e2e/user/session-refresh.spec.ts b/frontend/tests/e2e/user/session-refresh.spec.ts index 4195da718..325f9ee26 100644 --- a/frontend/tests/e2e/user/session-refresh.spec.ts +++ b/frontend/tests/e2e/user/session-refresh.spec.ts @@ -1,14 +1,10 @@ import {test, expect} from '../../support/fixtures' import {UserFactory} from '../../factories/user' +import {setupApiUrl} from '../../support/authenticateUser' import {TEST_PASSWORD} from '../../support/constants' async function loginViaBrowser(page, username: string) { - // Set the API URL so the frontend knows where to send requests. - const apiUrl = process.env.API_URL || 'http://127.0.0.1:3456/api/v1' - await page.addInitScript(({apiUrl}) => { - window.localStorage.setItem('API_URL', apiUrl) - window.API_URL = apiUrl - }, {apiUrl}) + await setupApiUrl(page) await page.goto('/login') await page.locator('input[id=username]').fill(username) diff --git a/frontend/tests/support/authenticateUser.ts b/frontend/tests/support/authenticateUser.ts index 50307b182..73d603221 100644 --- a/frontend/tests/support/authenticateUser.ts +++ b/frontend/tests/support/authenticateUser.ts @@ -2,6 +2,19 @@ import type {Page, APIRequestContext} from '@playwright/test' import {UserFactory} from '../factories/user' import {TEST_PASSWORD} from './constants' +/** + * Sets up the API URL in the page's localStorage and window so the frontend + * knows where to send requests. Call this before navigating to any page. + */ +export async function setupApiUrl(page: Page) { + // Use 127.0.0.1 instead of localhost to match the frontend's origin for CORS + const apiUrl = process.env.API_URL || 'http://127.0.0.1:3456/api/v1' + await page.addInitScript(({apiUrl}) => { + window.localStorage.setItem('API_URL', apiUrl) + window.API_URL = apiUrl + }, {apiUrl}) +} + /** * This authenticates a user and puts the token in local storage which allows us to perform authenticated requests. * Returns the user and token for use in tests that need to make authenticated API calls. @@ -28,15 +41,10 @@ export async function login(page: Page | null, apiContext: APIRequestContext, us // Set token and API_URL before navigating (only if page is provided) if (page) { - // Use 127.0.0.1 instead of localhost to match the frontend's origin for CORS - const apiUrl = process.env.API_URL || 'http://127.0.0.1:3456/api/v1' - await page.addInitScript(({token, apiUrl}) => { - // Set both localStorage AND window.API_URL - // The app uses window.API_URL for initialization (in base.ts loadApp) + await setupApiUrl(page) + await page.addInitScript(({token}) => { window.localStorage.setItem('token', token) - window.localStorage.setItem('API_URL', apiUrl) - window.API_URL = apiUrl - }, {token, apiUrl}) + }, {token}) } return {user, token} diff --git a/pkg/modules/auth/oauth2server/client_test.go b/pkg/modules/auth/oauth2server/client_test.go new file mode 100644 index 000000000..328e1fb55 --- /dev/null +++ b/pkg/modules/auth/oauth2server/client_test.go @@ -0,0 +1,50 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateRedirectURI(t *testing.T) { + t.Run("accepts vikunja-flutter scheme", func(t *testing.T) { + assert.True(t, ValidateRedirectURI("vikunja-flutter://callback")) + }) + t.Run("accepts vikunja-desktop scheme", func(t *testing.T) { + assert.True(t, ValidateRedirectURI("vikunja-desktop://auth")) + }) + t.Run("rejects https scheme", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("https://evil.com/callback")) + }) + t.Run("rejects http scheme", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("http://localhost/callback")) + }) + t.Run("rejects javascript scheme", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("javascript:alert(1)")) + }) + t.Run("rejects data scheme", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("data:text/html,")) + }) + t.Run("rejects non-vikunja custom scheme", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("myapp://callback")) + }) + t.Run("rejects empty URI", func(t *testing.T) { + assert.False(t, ValidateRedirectURI("")) + }) +} diff --git a/pkg/modules/auth/oauth2server/pkce_test.go b/pkg/modules/auth/oauth2server/pkce_test.go new file mode 100644 index 000000000..d7c55cc76 --- /dev/null +++ b/pkg/modules/auth/oauth2server/pkce_test.go @@ -0,0 +1,51 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "crypto/sha256" + "encoding/base64" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVerifyPKCE(t *testing.T) { + t.Run("valid S256 verifier", func(t *testing.T) { + verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + h := sha256.Sum256([]byte(verifier)) + challenge := base64.RawURLEncoding.EncodeToString(h[:]) + + assert.True(t, VerifyPKCE(verifier, challenge, "S256")) + }) + + t.Run("wrong verifier", func(t *testing.T) { + verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + h := sha256.Sum256([]byte(verifier)) + challenge := base64.RawURLEncoding.EncodeToString(h[:]) + + assert.False(t, VerifyPKCE("wrong-verifier", challenge, "S256")) + }) + + t.Run("unsupported method", func(t *testing.T) { + assert.False(t, VerifyPKCE("verifier", "challenge", "plain")) + }) + + t.Run("empty method", func(t *testing.T) { + assert.False(t, VerifyPKCE("verifier", "challenge", "")) + }) +} diff --git a/pkg/webtests/oauth2_test.go b/pkg/webtests/oauth2_test.go new file mode 100644 index 000000000..b1ec97eb3 --- /dev/null +++ b/pkg/webtests/oauth2_test.go @@ -0,0 +1,308 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package webtests + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/modules/auth/oauth2server" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// authorizeRequestBody builds a JSON body for the authorize endpoint. +func authorizeRequestBody(responseType, clientID, redirectURI, codeChallenge, codeChallengeMethod, state string) []byte { + body, _ := json.Marshal(map[string]string{ //nolint:errchkjson + "response_type": responseType, + "client_id": clientID, + "redirect_uri": redirectURI, + "code_challenge": codeChallenge, + "code_challenge_method": codeChallengeMethod, + "state": state, + }) + return body +} + +// doAuthorize performs a POST to /api/v1/oauth/authorize with the given JWT token and returns the recorder. +func doAuthorize(e http.Handler, token string, body []byte) *httptest.ResponseRecorder { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/oauth/authorize", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + e.ServeHTTP(rec, req) + return rec +} + +// doTokenRequest performs a JSON POST to /api/v1/oauth/token and returns the recorder. +func doTokenRequest(e http.Handler, params map[string]string) *httptest.ResponseRecorder { + body, _ := json.Marshal(params) //nolint:errchkjson + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/oauth/token", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + e.ServeHTTP(rec, req) + return rec +} + +func TestOAuth2AuthorizeEndpoint(t *testing.T) { + t.Run("rejects unauthenticated request", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + body := authorizeRequestBody("code", "vikunja", "vikunja-flutter://callback", "abc123", "S256", "teststate") + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/oauth/authorize", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + e.ServeHTTP(rec, req) + assert.Equal(t, http.StatusUnauthorized, rec.Code) + }) + + t.Run("issues code for authenticated user", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + token, err := auth.NewUserJWTAuthtoken(&testuser1, "test-session-id") + require.NoError(t, err) + + body := authorizeRequestBody("code", "vikunja", "vikunja-flutter://callback", "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "S256", "teststate") + rec := doAuthorize(e, token, body) + + require.Equal(t, http.StatusOK, rec.Code) + + var resp oauth2server.AuthorizeResponse + err = json.Unmarshal(rec.Body.Bytes(), &resp) + require.NoError(t, err) + assert.NotEmpty(t, resp.Code) + assert.Equal(t, "vikunja-flutter://callback", resp.RedirectURI) + assert.Equal(t, "teststate", resp.State) + }) + + t.Run("rejects invalid redirect_uri", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + token, err := auth.NewUserJWTAuthtoken(&testuser1, "test-session-id") + require.NoError(t, err) + + body := authorizeRequestBody("code", "vikunja", "https://evil.com/callback", "test", "S256", "") + rec := doAuthorize(e, token, body) + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) + + t.Run("rejects missing PKCE", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + token, err := auth.NewUserJWTAuthtoken(&testuser1, "test-session-id") + require.NoError(t, err) + + body := authorizeRequestBody("code", "vikunja", "vikunja-flutter://callback", "", "", "") + rec := doAuthorize(e, token, body) + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) +} + +// getAuthorizationCode performs the authorize step and returns the code from the JSON response. +func getAuthorizationCode(t *testing.T, e http.Handler, codeChallenge, state string) string { + t.Helper() + + token, err := auth.NewUserJWTAuthtoken(&testuser1, "test-session-id") + require.NoError(t, err) + + body := authorizeRequestBody("code", "vikunja", "vikunja-flutter://callback", codeChallenge, "S256", state) + rec := doAuthorize(e, token, body) + require.Equal(t, http.StatusOK, rec.Code) + + var resp oauth2server.AuthorizeResponse + err = json.Unmarshal(rec.Body.Bytes(), &resp) + require.NoError(t, err) + require.NotEmpty(t, resp.Code) + + return resp.Code +} + +func TestOAuth2TokenEndpoint(t *testing.T) { + t.Run("full authorization code flow with PKCE", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + codeVerifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + h := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(h[:]) + + code := getAuthorizationCode(t, e, codeChallenge, "xyz") + + rec := doTokenRequest(e, map[string]string{ + "grant_type": "authorization_code", + "code": code, + "client_id": "vikunja", + "redirect_uri": "vikunja-flutter://callback", + "code_verifier": codeVerifier, + }) + + require.Equal(t, http.StatusOK, rec.Code) + + var tokenResp oauth2server.TokenResponse + err = json.Unmarshal(rec.Body.Bytes(), &tokenResp) + require.NoError(t, err) + assert.NotEmpty(t, tokenResp.AccessToken) + assert.Equal(t, "bearer", tokenResp.TokenType) + assert.NotEmpty(t, tokenResp.RefreshToken) + assert.Positive(t, tokenResp.ExpiresIn) + }) + + t.Run("code is single-use", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + codeVerifier := "test-verifier-for-single-use-check" + h := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(h[:]) + + code := getAuthorizationCode(t, e, codeChallenge, "") + + tokenParams := map[string]string{ + "grant_type": "authorization_code", + "code": code, + "client_id": "vikunja", + "redirect_uri": "vikunja-flutter://callback", + "code_verifier": codeVerifier, + } + + // First exchange succeeds + rec := doTokenRequest(e, tokenParams) + require.Equal(t, http.StatusOK, rec.Code) + + // Second exchange fails + rec2 := doTokenRequest(e, tokenParams) + assert.Equal(t, http.StatusBadRequest, rec2.Code) + }) + + t.Run("rejects wrong PKCE verifier", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + codeVerifier := "correct-verifier" + h := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(h[:]) + + code := getAuthorizationCode(t, e, codeChallenge, "") + + rec := doTokenRequest(e, map[string]string{ + "grant_type": "authorization_code", + "code": code, + "client_id": "vikunja", + "redirect_uri": "vikunja-flutter://callback", + "code_verifier": "wrong-verifier", + }) + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) + + t.Run("rejects invalid grant_type", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + rec := doTokenRequest(e, map[string]string{ + "grant_type": "password", + "client_id": "vikunja", + }) + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) + + t.Run("refresh token flow", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + codeVerifier := "refresh-flow-test-verifier" + h := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(h[:]) + + code := getAuthorizationCode(t, e, codeChallenge, "") + + rec := doTokenRequest(e, map[string]string{ + "grant_type": "authorization_code", + "code": code, + "client_id": "vikunja", + "redirect_uri": "vikunja-flutter://callback", + "code_verifier": codeVerifier, + }) + require.Equal(t, http.StatusOK, rec.Code) + + var tokenResp oauth2server.TokenResponse + _ = json.Unmarshal(rec.Body.Bytes(), &tokenResp) + + // Use the refresh token to get new tokens + rec2 := doTokenRequest(e, map[string]string{ + "grant_type": "refresh_token", + "refresh_token": tokenResp.RefreshToken, + "client_id": "vikunja", + }) + + require.Equal(t, http.StatusOK, rec2.Code) + + var refreshResp oauth2server.TokenResponse + err = json.Unmarshal(rec2.Body.Bytes(), &refreshResp) + require.NoError(t, err) + assert.NotEmpty(t, refreshResp.AccessToken) + assert.NotEmpty(t, refreshResp.RefreshToken) + assert.NotEqual(t, tokenResp.RefreshToken, refreshResp.RefreshToken) + }) + + t.Run("refresh token rotation prevents replay", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + codeVerifier := "replay-test-verifier" + h := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(h[:]) + + code := getAuthorizationCode(t, e, codeChallenge, "") + + rec := doTokenRequest(e, map[string]string{ + "grant_type": "authorization_code", + "code": code, + "client_id": "vikunja", + "redirect_uri": "vikunja-flutter://callback", + "code_verifier": codeVerifier, + }) + + var tokenResp oauth2server.TokenResponse + _ = json.Unmarshal(rec.Body.Bytes(), &tokenResp) + oldRefreshToken := tokenResp.RefreshToken + + refreshParams := map[string]string{ + "grant_type": "refresh_token", + "refresh_token": oldRefreshToken, + "client_id": "vikunja", + } + + // First refresh succeeds + rec2 := doTokenRequest(e, refreshParams) + require.Equal(t, http.StatusOK, rec2.Code) + + // Replay the same old refresh token — should fail + rec3 := doTokenRequest(e, refreshParams) + assert.Equal(t, http.StatusUnauthorized, rec3.Code) + }) +} From 73eb8279ae816cc8dface89c594b05e5fc6c1e3f Mon Sep 17 00:00:00 2001 From: surfingbytes Date: Thu, 26 Mar 2026 17:36:29 +0000 Subject: [PATCH 011/501] chore: add .pnpm-store to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c9089f1b9..8a57c1e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ mage-static /plugins/* /plugins-dev/* +# pnpm +.pnpm-store/ + # Devenv .devenv* devenv.local.nix From 8d958aef62b0d6f8e40478e674bfe0b6189b1ef5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:17:43 +0000 Subject: [PATCH 012/501] chore(deps): update dev-dependencies --- desktop/package.json | 2 +- desktop/pnpm-lock.yaml | 10 +- frontend/package.json | 6 +- frontend/pnpm-lock.yaml | 288 ++++++++++++++++------------------------ 4 files changed, 121 insertions(+), 185 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index 68637148a..383665e14 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -52,7 +52,7 @@ } }, "devDependencies": { - "electron": "40.8.4", + "electron": "40.8.5", "electron-builder": "26.8.1", "unzipper": "0.12.3" }, diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 0bcb7cb9c..8d32233f5 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -19,8 +19,8 @@ importers: version: 5.2.1 devDependencies: electron: - specifier: 40.8.4 - version: 40.8.4 + specifier: 40.8.5 + version: 40.8.5 electron-builder: specifier: 26.8.1 version: 26.8.1(electron-builder-squirrel-windows@24.13.3) @@ -553,8 +553,8 @@ packages: electron-publish@26.8.1: resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==} - electron@40.8.4: - resolution: {integrity: sha512-7AoSakFr+g2CTukHDS79cqNiaWPoD8bQ4kIahwUUVv0O5fy4BfZawVCxOFLc61POq8xDvqMSDKPfeFXK/Coc5g==} + electron@40.8.5: + resolution: {integrity: sha512-pgTY/VPQKaiU4sTjfU96iyxCXrFm4htVPCMRT4b7q9ijNTRgtLmLvcmzp2G4e7xDrq9p7OLHSmu1rBKFf6Y1/A==} engines: {node: '>= 12.20.55'} hasBin: true @@ -2340,7 +2340,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron@40.8.4: + electron@40.8.5: dependencies: '@electron/get': 2.0.3 '@types/node': 24.10.9 diff --git a/frontend/package.json b/frontend/package.json index c0f7b9ea7..7b9f4a9a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -131,7 +131,7 @@ "eslint": "9.39.4", "eslint-plugin-depend": "1.5.0", "eslint-plugin-vue": "10.8.0", - "happy-dom": "20.8.8", + "happy-dom": "20.8.9", "histoire": "1.0.0-beta.1", "postcss": "8.5.8", "postcss-easing-gradients": "3.0.1", @@ -139,7 +139,7 @@ "rollup": "4.60.0", "rollup-plugin-visualizer": "6.0.11", "sass-embedded": "1.98.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-property-sort-order-smacss": "10.0.0", "stylelint-config-recommended-vue": "1.6.1", "stylelint-config-standard-scss": "17.0.0", @@ -151,7 +151,7 @@ "vite-plugin-pwa": "1.2.0", "vite-plugin-vue-devtools": "8.1.1", "vite-svg-loader": "5.1.1", - "vitest": "4.1.1", + "vitest": "4.1.2", "vue-tsc": "3.2.6", "wait-on": "9.0.4", "workbox-cli": "7.4.0" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index bd63b5c3c..d798f3b76 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -251,8 +251,8 @@ importers: specifier: 10.8.0 version: 10.8.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.4.2))(typescript@5.9.3))(eslint@9.39.4(jiti@2.4.2))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.4.2))) happy-dom: - specifier: 20.8.8 - version: 20.8.8 + specifier: 20.8.9 + version: 20.8.9 histoire: specifier: 1.0.0-beta.1 version: 1.0.0-beta.1(@types/node@24.12.0)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) @@ -275,20 +275,20 @@ importers: specifier: 1.98.0 version: 1.98.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-property-sort-order-smacss: specifier: 10.0.0 - version: 10.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 10.0.0(stylelint@17.6.0(typescript@5.9.3)) stylelint-config-recommended-vue: specifier: 1.6.1 - version: 1.6.1(postcss-html@1.8.0)(stylelint@17.5.0(typescript@5.9.3)) + version: 1.6.1(postcss-html@1.8.0)(stylelint@17.6.0(typescript@5.9.3)) stylelint-config-standard-scss: specifier: 17.0.0 - version: 17.0.0(postcss@8.5.8)(stylelint@17.5.0(typescript@5.9.3)) + version: 17.0.0(postcss@8.5.8)(stylelint@17.6.0(typescript@5.9.3)) stylelint-use-logical: specifier: 2.1.3 - version: 2.1.3(stylelint@17.5.0(typescript@5.9.3)) + version: 2.1.3(stylelint@17.6.0(typescript@5.9.3)) tailwindcss: specifier: 4.2.2 version: 4.2.2 @@ -311,8 +311,8 @@ importers: specifier: 5.1.1 version: 5.1.1(vue@3.5.27(typescript@5.9.3)) vitest: - specifier: 4.1.1 - version: 4.1.1(@types/node@24.12.0)(happy-dom@20.8.8)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)) + specifier: 4.1.2 + version: 4.1.2(@types/node@24.12.0)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)) vue-tsc: specifier: 3.2.6 version: 3.2.6(typescript@5.9.3) @@ -964,9 +964,6 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.28': - resolution: {integrity: sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==} - '@csstools/css-syntax-patches-for-csstree@1.1.1': resolution: {integrity: sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==} peerDependencies: @@ -2852,11 +2849,11 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 - '@vitest/expect@4.1.1': - resolution: {integrity: sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==} + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} - '@vitest/mocker@4.1.1': - resolution: {integrity: sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==} + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2866,20 +2863,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.1': - resolution: {integrity: sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==} + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} - '@vitest/runner@4.1.1': - resolution: {integrity: sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==} + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} - '@vitest/snapshot@4.1.1': - resolution: {integrity: sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==} + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} - '@vitest/spy@4.1.1': - resolution: {integrity: sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==} + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} - '@vitest/utils@4.1.1': - resolution: {integrity: sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==} + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -3057,10 +3054,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} @@ -3426,15 +3419,6 @@ packages: core-js-compat@3.38.1: resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} @@ -3491,10 +3475,6 @@ packages: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -4084,10 +4064,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -4188,8 +4164,8 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - happy-dom@20.8.8: - resolution: {integrity: sha512-5/F8wxkNxYtsN0bXfMwIyNLZ9WYsoOYPbmoluqVJqv8KBUbcyKZawJ7uYK4WTX8IHBLYv+VXIwfeNDPy1oKMwQ==} + happy-dom@20.8.9: + resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} engines: {node: '>=20.0.0'} hard-rejection@2.1.0: @@ -4841,12 +4817,6 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - - mdn-data@2.26.0: - resolution: {integrity: sha512-ZqI0qjKWHMPcGUfLmlr80NPNVHIOjPMHtIOe1qXYFGS0YBZ1YKAzo9yk8W+gGrLCN0Xdv/RKxqdIsqPakEfmow==} - mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -6005,10 +5975,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-ansi@7.2.0: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} @@ -6117,8 +6083,8 @@ packages: peerDependencies: stylelint: '>= 11 < 18' - stylelint@17.5.0: - resolution: {integrity: sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==} + stylelint@17.6.0: + resolution: {integrity: sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg==} engines: {node: '>=20.19.0'} hasBin: true @@ -6212,8 +6178,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} tldts-core@6.1.58: @@ -6560,18 +6526,18 @@ packages: yaml: optional: true - vitest@4.1.1: - resolution: {integrity: sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==} + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.1 - '@vitest/browser-preview': 4.1.1 - '@vitest/browser-webdriverio': 4.1.1 - '@vitest/ui': 4.1.1 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6823,8 +6789,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@7.0.0: - resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==} + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} ws@8.19.0: @@ -6934,7 +6900,7 @@ snapshots: dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 - css-tree: 3.1.0 + css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 lru-cache: 11.2.4 @@ -7736,8 +7702,6 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.28': {} - '@csstools/css-syntax-patches-for-csstree@1.1.1(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 @@ -8471,7 +8435,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -9527,46 +9491,46 @@ snapshots: vite: 7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3) vue: 3.5.27(typescript@5.9.3) - '@vitest/expect@4.1.1': + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.2 - '@vitest/spy': 4.1.1 - '@vitest/utils': 4.1.1 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 chai: 6.2.2 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.1 + '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3) - '@vitest/pretty-format@4.1.1': + '@vitest/pretty-format@4.1.2': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@vitest/runner@4.1.1': + '@vitest/runner@4.1.2': dependencies: - '@vitest/utils': 4.1.1 + '@vitest/utils': 4.1.2 pathe: 2.0.3 - '@vitest/snapshot@4.1.1': + '@vitest/snapshot@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.1 - '@vitest/utils': 4.1.1 + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.1': {} + '@vitest/spy@4.1.2': {} - '@vitest/utils@4.1.1': + '@vitest/utils@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.1 + '@vitest/pretty-format': 4.1.2 convert-source-map: 2.0.0 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@volar/language-core@2.4.28': dependencies: @@ -9799,8 +9763,6 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} - ansi-regex@6.2.2: {} ansi-styles@4.3.0: @@ -10174,15 +10136,6 @@ snapshots: dependencies: browserslist: 4.28.1 - cosmiconfig@9.0.0(typescript@5.9.3): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.0 - js-yaml: 4.1.1 - parse-json: 5.2.0 - optionalDependencies: - typescript: 5.9.3 - cosmiconfig@9.0.1(typescript@5.9.3): dependencies: env-paths: 2.2.1 @@ -10240,11 +10193,6 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.1 - css-tree@3.1.0: - dependencies: - mdn-data: 2.12.2 - source-map-js: 1.2.1 - css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -10263,8 +10211,8 @@ snapshots: cssstyle@5.3.7: dependencies: '@asamuzakjp/css-color': 4.1.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.28 - css-tree: 3.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.1(css-tree@3.2.1) + css-tree: 3.2.1 lru-cache: 11.2.4 csstype@3.2.3: {} @@ -10906,8 +10854,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: @@ -11037,7 +10983,7 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - happy-dom@20.8.8: + happy-dom@20.8.9: dependencies: '@types/node': 24.12.0 '@types/whatwg-mimetype': 3.0.2 @@ -11689,10 +11635,6 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.12.2: {} - - mdn-data@2.26.0: {} - mdn-data@2.27.1: {} mdurl@2.0.0: {} @@ -12441,7 +12383,7 @@ snapshots: dependencies: '@puppeteer/browsers': 2.6.1 chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) - cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig: 9.0.1(typescript@5.9.3) devtools-protocol: 0.0.1367902 puppeteer-core: 23.11.1 typed-query-selector: 2.12.0 @@ -12934,13 +12876,13 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 string-width@8.2.0: dependencies: @@ -13000,10 +12942,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.0.1 - strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -13028,78 +12966,78 @@ snapshots: style-mod@4.1.2: {} - stylelint-config-html@1.1.0(postcss-html@1.8.0)(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-html@1.1.0(postcss-html@1.8.0)(stylelint@17.6.0(typescript@5.9.3)): dependencies: postcss-html: 1.8.0 - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-config-property-sort-order-smacss@10.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-property-sort-order-smacss@10.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: css-property-sort-order-smacss: 2.2.0 - stylelint: 17.5.0(typescript@5.9.3) - stylelint-order: 6.0.4(stylelint@17.5.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-order: 6.0.4(stylelint@17.6.0(typescript@5.9.3)) - stylelint-config-recommended-scss@17.0.0(postcss@8.5.8)(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-recommended-scss@17.0.0(postcss@8.5.8)(stylelint@17.6.0(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.8) - stylelint: 17.5.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.5.0(typescript@5.9.3)) - stylelint-scss: 7.0.0(stylelint@17.5.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.6.0(typescript@5.9.3)) + stylelint-scss: 7.0.0(stylelint@17.6.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.8 - stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.0)(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.0)(stylelint@17.6.0(typescript@5.9.3)): dependencies: postcss-html: 1.8.0 semver: 7.7.1 - stylelint: 17.5.0(typescript@5.9.3) - stylelint-config-html: 1.1.0(postcss-html@1.8.0)(stylelint@17.5.0(typescript@5.9.3)) - stylelint-config-recommended: 17.0.0(stylelint@17.5.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-config-html: 1.1.0(postcss-html@1.8.0)(stylelint@17.6.0(typescript@5.9.3)) + stylelint-config-recommended: 17.0.0(stylelint@17.6.0(typescript@5.9.3)) - stylelint-config-recommended@17.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-recommended@17.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-config-recommended@18.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-recommended@18.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-config-standard-scss@17.0.0(postcss@8.5.8)(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-standard-scss@17.0.0(postcss@8.5.8)(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) - stylelint-config-recommended-scss: 17.0.0(postcss@8.5.8)(stylelint@17.5.0(typescript@5.9.3)) - stylelint-config-standard: 40.0.0(stylelint@17.5.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-config-recommended-scss: 17.0.0(postcss@8.5.8)(stylelint@17.6.0(typescript@5.9.3)) + stylelint-config-standard: 40.0.0(stylelint@17.6.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.8 - stylelint-config-standard@40.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-standard@40.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.5.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.6.0(typescript@5.9.3)) - stylelint-order@6.0.4(stylelint@17.5.0(typescript@5.9.3)): + stylelint-order@6.0.4(stylelint@17.6.0(typescript@5.9.3)): dependencies: postcss: 8.5.8 postcss-sorting: 8.0.2(postcss@8.5.8) - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-scss@7.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-scss@7.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - css-tree: 3.1.0 + css-tree: 3.2.1 is-plain-object: 5.0.0 known-css-properties: 0.37.0 - mdn-data: 2.26.0 + mdn-data: 2.27.1 postcss-media-query-parser: 0.2.3 postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-use-logical@2.1.3(stylelint@17.5.0(typescript@5.9.3)): + stylelint-use-logical@2.1.3(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint@17.5.0(typescript@5.9.3): + stylelint@17.6.0(typescript@5.9.3): dependencies: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -13122,7 +13060,6 @@ snapshots: html-tags: 5.1.0 ignore: 7.0.5 import-meta-resolve: 4.2.0 - imurmurhash: 0.1.4 is-plain-object: 5.0.0 mathml-tag-names: 4.0.0 meow: 14.1.0 @@ -13137,7 +13074,7 @@ snapshots: supports-hyperlinks: 4.4.0 svg-tags: 1.0.0 table: 6.9.0 - write-file-atomic: 7.0.0 + write-file-atomic: 7.0.1 transitivePeerDependencies: - supports-color - typescript @@ -13242,7 +13179,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - tinyrainbow@3.0.3: {} + tinyrainbow@3.1.0: {} tldts-core@6.1.58: {} @@ -13617,15 +13554,15 @@ snapshots: terser: 5.31.6 yaml: 2.8.3 - vitest@4.1.1(@types/node@24.12.0)(happy-dom@20.8.8)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)): + vitest@4.1.2(@types/node@24.12.0)(happy-dom@20.8.9)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.1 - '@vitest/mocker': 4.1.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.1 - '@vitest/runner': 4.1.1 - '@vitest/snapshot': 4.1.1 - '@vitest/spy': 4.1.1 - '@vitest/utils': 4.1.1 + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -13636,12 +13573,12 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.0.2 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 vite: 7.3.1(@types/node@24.12.0)(jiti@2.4.2)(lightningcss@1.32.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.31.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.0 - happy-dom: 20.8.8 + happy-dom: 20.8.9 jsdom: 27.4.0 transitivePeerDependencies: - msw @@ -13947,19 +13884,18 @@ snapshots: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.1 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrappy@1.0.2: {} - write-file-atomic@7.0.0: + write-file-atomic@7.0.1: dependencies: - imurmurhash: 0.1.4 signal-exit: 4.1.0 ws@8.19.0: {} From ffb291c966dee7308fd719c3e56237e79cfed14e Mon Sep 17 00:00:00 2001 From: "Frederick [Bot]" Date: Sat, 28 Mar 2026 01:16:36 +0000 Subject: [PATCH 013/501] chore(i18n): update translations via Crowdin --- frontend/src/i18n/lang/de-DE.json | 9 ++++++++- frontend/src/i18n/lang/de-swiss.json | 9 ++++++++- frontend/src/i18n/lang/ru-RU.json | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json index f48d05a29..49f348167 100644 --- a/frontend/src/i18n/lang/de-DE.json +++ b/frontend/src/i18n/lang/de-DE.json @@ -147,8 +147,15 @@ }, "caldav": { "title": "CalDAV", + "howTo": "Du kannst Vikunja mit CalDAV-Clients verbinden, um alle Aufgaben mit verschiedenen Clients anzuzeigen und zu verwalten. Gebe dazu diese URL in deinen Client ein:", "more": "Weitere Informationen über CalDAV in Vikunja", - "tokens": "CalDAV-Token" + "tokens": "CalDAV-Token", + "tokensHowTo": "Für die CalDAV-Authentifizierung kannst du entweder dein normales Passwort oder einen CalDAV-Token verwenden.", + "createToken": "CalDAV-Token erstellen", + "tokenCreated": "Hier ist dein neues Token: {token}", + "wontSeeItAgain": "Schreib es auf oder speicher es sicher — du wirst es nicht nochmal sehen können.", + "mustUseToken": "Du musst einen CalDAV Token erstellen, um CalDAV mit einem Drittanbieter-Client zu nutzen. Verwende diesen Token anstelle deines Passworts.", + "usernameIs": "Dein Anmeldename für CalDAV lautet: {0}" }, "avatar": { "title": "Avatar", diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json index b735c0e4a..825e71013 100644 --- a/frontend/src/i18n/lang/de-swiss.json +++ b/frontend/src/i18n/lang/de-swiss.json @@ -147,8 +147,15 @@ }, "caldav": { "title": "CalDAV", + "howTo": "Du kannst Vikunja mit CalDAV-Clients verbinden, um alle Aufgaben mit verschiedenen Clients anzuzeigen und zu verwalten. Gebe dazu diese URL in deinen Client ein:", "more": "Weitere Informationen über CalDAV in Vikunja", - "tokens": "CalDAV-Token" + "tokens": "CalDAV-Token", + "tokensHowTo": "Für die CalDAV-Authentifizierung kannst du entweder dein normales Passwort oder einen CalDAV-Token verwenden.", + "createToken": "CalDAV-Token erstellen", + "tokenCreated": "Hier ist dein neues Token: {token}", + "wontSeeItAgain": "Schreib es auf oder speicher es sicher — du wirst es nicht nochmal sehen können.", + "mustUseToken": "Du musst einen CalDAV Token erstellen, um CalDAV mit einem Drittanbieter-Client zu nutzen. Verwende diesen Token anstelle deines Passworts.", + "usernameIs": "Dein Anmeldename für CalDAV lautet: {0}" }, "avatar": { "title": "Herr Der Elemente", diff --git a/frontend/src/i18n/lang/ru-RU.json b/frontend/src/i18n/lang/ru-RU.json index d44475006..1f52124a2 100644 --- a/frontend/src/i18n/lang/ru-RU.json +++ b/frontend/src/i18n/lang/ru-RU.json @@ -147,8 +147,15 @@ }, "caldav": { "title": "CalDAV", + "howTo": "Вы можете подключить Vikunja к клиентам CalDAV, чтобы просматривать и управлять всеми своими задачами из разных клиентов. Введите этот URL в клиенте:", "more": "Подробнее о CalDAV в Vikunja", - "tokens": "Токены CalDAV" + "tokens": "Токены CalDAV", + "tokensHowTo": "Для аутентификации в CalDAV можно использовать обычный пароль или отдельный токен CalDAV.", + "createToken": "Создать токен CalDAV", + "tokenCreated": "Ваш новый токен: {token}", + "wontSeeItAgain": "Запишите его где-нибудь — у вас больше не будет возможности его увидеть.", + "mustUseToken": "Вам необходимо создать токен CalDAV, если вы хотите использовать его со сторонним клиентом. Используйте его в качестве пароля.", + "usernameIs": "Имя пользователя для CalDAV: {0}" }, "avatar": { "title": "Аватар", From 23415c57aa7c56305e32ee1339c7766a494a4d2e Mon Sep 17 00:00:00 2001 From: j-hugo <67948298+j-hugo@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:43:58 +0100 Subject: [PATCH 014/501] docs: correct task comment endpoint description and title (#2498) --- pkg/models/task_comments.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index 4e9ab0ffa..e079f54e0 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -219,8 +219,8 @@ func getTaskCommentSimple(s *xorm.Session, tc *TaskComment) error { } // ReadOne handles getting a single comment -// @Summary Remove a task comment -// @Description Remove a task comment. The user doing this need to have at least read access to the task this comment belongs to. +// @Summary Get a task comment +// @Description Get a task comment. The user doing this need to have at least read access to the task this comment belongs to. // @tags task // @Accept json // @Produce json From fa2dc8f9183029a6cf944b60f7036315edfb0c19 Mon Sep 17 00:00:00 2001 From: "Frederick [Bot]" Date: Sat, 28 Mar 2026 23:53:53 +0000 Subject: [PATCH 015/501] [skip ci] Updated swagger docs --- pkg/swagger/docs.go | 4 ++-- pkg/swagger/swagger.json | 4 ++-- pkg/swagger/swagger.yaml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 35dfe168d..76bc66cc8 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -5094,7 +5094,7 @@ const docTemplate = `{ "JWTKeyAuth": [] } ], - "description": "Remove a task comment. The user doing this need to have at least read access to the task this comment belongs to.", + "description": "Get a task comment. The user doing this need to have at least read access to the task this comment belongs to.", "consumes": [ "application/json" ], @@ -5104,7 +5104,7 @@ const docTemplate = `{ "tags": [ "task" ], - "summary": "Remove a task comment", + "summary": "Get a task comment", "parameters": [ { "type": "integer", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index ed5eb8e20..e3c17d1ac 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -5086,7 +5086,7 @@ "JWTKeyAuth": [] } ], - "description": "Remove a task comment. The user doing this need to have at least read access to the task this comment belongs to.", + "description": "Get a task comment. The user doing this need to have at least read access to the task this comment belongs to.", "consumes": [ "application/json" ], @@ -5096,7 +5096,7 @@ "tags": [ "task" ], - "summary": "Remove a task comment", + "summary": "Get a task comment", "parameters": [ { "type": "integer", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 15aa0815f..7e4e1b129 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -5227,8 +5227,8 @@ paths: get: consumes: - application/json - description: Remove a task comment. The user doing this need to have at least - read access to the task this comment belongs to. + description: Get a task comment. The user doing this need to have at least read + access to the task this comment belongs to. parameters: - description: Task ID in: path @@ -5261,7 +5261,7 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Remove a task comment + summary: Get a task comment tags: - task post: From 21a450b21f3cd02d4faefd6d3a7d17aa7a3127b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:22:57 +0000 Subject: [PATCH 016/501] chore(deps): bump serialize-javascript from 7.0.3 to 7.0.5 in /frontend Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 7.0.3 to 7.0.5. - [Release notes](https://github.com/yahoo/serialize-javascript/releases) - [Commits](https://github.com/yahoo/serialize-javascript/compare/v7.0.3...v7.0.5) --- updated-dependencies: - dependency-name: serialize-javascript dependency-version: 7.0.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 7b9f4a9a8..3cc84c67a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -168,7 +168,7 @@ "minimatch": "^10.2.3", "rollup": "$rollup", "basic-ftp": "5.2.0", - "serialize-javascript": "^7.0.3", + "serialize-javascript": "^7.0.5", "flatted": "^3.4.1" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index d798f3b76..2ca7b9608 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,7 +8,7 @@ overrides: minimatch: ^10.2.3 rollup: 4.60.0 basic-ftp: 5.2.0 - serialize-javascript: ^7.0.3 + serialize-javascript: ^7.0.5 flatted: ^3.4.1 importers: @@ -5783,8 +5783,8 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@7.0.3: - resolution: {integrity: sha512-h+cZ/XXarqDgCjo+YSyQU/ulDEESGGf8AMK9pPNmhNSl/FzPl6L8pMp1leca5z6NuG6tvV/auC8/43tmovowww==} + serialize-javascript@7.0.5: + resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==} engines: {node: '>=20.0.0'} set-function-length@1.2.2: @@ -8656,7 +8656,7 @@ snapshots: '@rollup/plugin-terser@0.4.4(rollup@4.60.0)': dependencies: - serialize-javascript: 7.0.3 + serialize-javascript: 7.0.5 smob: 1.5.0 terser: 5.31.6 optionalDependencies: @@ -12708,7 +12708,7 @@ snapshots: semver@7.7.3: {} - serialize-javascript@7.0.3: {} + serialize-javascript@7.0.5: {} set-function-length@1.2.2: dependencies: From fb8c937d77a61177de4271c0a9d21ffbecc41e3f Mon Sep 17 00:00:00 2001 From: "Frederick [Bot]" Date: Sun, 29 Mar 2026 01:25:40 +0000 Subject: [PATCH 017/501] chore(i18n): update translations via Crowdin --- frontend/src/i18n/lang/de-DE.json | 1 + frontend/src/i18n/lang/de-swiss.json | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json index 49f348167..376381d77 100644 --- a/frontend/src/i18n/lang/de-DE.json +++ b/frontend/src/i18n/lang/de-DE.json @@ -54,6 +54,7 @@ "authenticating": "Authentifizierung…", "openIdStateError": "Zustand stimmt nicht überein, fahre nicht fort!", "openIdGeneralError": "Es ist ein Fehler bei der externen Authentifizierung aufgetreten.", + "oauthMissingParams": "Erforderliche OAuth-Parameter fehlen: {params}", "logout": "Abmelden", "emailInvalid": "Bitte gib eine gültige E-Mail-Adresse ein.", "usernameRequired": "Bitte gib einen Anmeldenamen ein.", diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json index 825e71013..215505bd2 100644 --- a/frontend/src/i18n/lang/de-swiss.json +++ b/frontend/src/i18n/lang/de-swiss.json @@ -54,6 +54,7 @@ "authenticating": "Authentifiziere…", "openIdStateError": "Status stimmt nid überiih, ich verweigerä wiiter zmache!", "openIdGeneralError": "Es ist ein Fehler bei der externen Authentifizierung aufgetreten.", + "oauthMissingParams": "Erforderliche OAuth-Parameter fehlen: {params}", "logout": "Uuslogge", "emailInvalid": "Bitte gib eine gültige E-Mail-Adresse ein.", "usernameRequired": "Bitte gib einen Anmeldenamen ein.", From 83bac158411d9564840e536578986738782f22c0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 30 Mar 2026 12:07:01 +0200 Subject: [PATCH 018/501] feat: rename ServiceJWTSecret to ServiceSecret with deprecation (#2502) --- config-raw.json | 7 ++++++- pkg/config/config.go | 16 ++++++++++++++-- pkg/doctor/config.go | 2 +- pkg/e2etests/integrations.go | 2 +- pkg/modules/auth/auth.go | 4 ++-- pkg/routes/api_tokens.go | 2 +- pkg/webtests/integrations.go | 4 ++-- 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/config-raw.json b/config-raw.json index 6bb00e6cf..b43345f7b 100644 --- a/config-raw.json +++ b/config-raw.json @@ -3,10 +3,15 @@ { "key": "service", "children": [ + { + "key": "secret", + "default_value": "\u003ca-secret\u003e", + "comment": "This secret is used to sign JWT tokens and for other cryptographic operations.\nDefault is a random secret which will be generated at each startup of Vikunja.\n(This means all already issued tokens will be invalid once you restart Vikunja)" + }, { "key": "JWTSecret", "default_value": "\u003cjwt-secret\u003e", - "comment": "This token is used to verify issued JWT tokens.\nDefault is a random token which will be generated at each startup of Vikunja.\n(This means all already issued tokens will be invalid once you restart Vikunja)" + "comment": "Deprecated: use service.secret instead. If set, its value will be copied to service.secret." }, { "key": "jwtttl", diff --git a/pkg/config/config.go b/pkg/config/config.go index c1bac6ee9..764f33196 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -41,7 +41,8 @@ type Key string // These constants hold all config value keys const ( // #nosec - ServiceJWTSecret Key = `service.JWTSecret` + ServiceSecret Key = `service.secret` + ServiceJWTSecret Key = `service.JWTSecret` // #nosec G101 -- Deprecated config key alias, not a credential ServiceJWTTTL Key = `service.jwtttl` ServiceJWTTTLLong Key = `service.jwtttllong` ServiceJWTTTLShort Key = `service.jwtttlshort` @@ -333,7 +334,7 @@ func InitDefaultConfig() { } // Service - ServiceJWTSecret.setDefault(random) + ServiceSecret.setDefault(random) ServiceJWTTTL.setDefault(259200) // 72 hours ServiceJWTTTLLong.setDefault(2592000) // 30 days ServiceJWTTTLShort.setDefault(600) // 10 minutes @@ -635,6 +636,17 @@ func InitConfig() { readConfigValuesFromFiles() + // Deprecation: migrate service.JWTSecret → service.secret only when the + // user has not explicitly set service.secret (so the new key takes precedence). + if ServiceJWTSecret.GetString() != "" { + if viper.IsSet(string(ServiceSecret)) { + log.Warning("config: both service.secret and service.jwtsecret are set. Using service.secret. Please remove service.jwtsecret, it is deprecated and will be removed in a future release.") + } else { + log.Warning("config: service.jwtsecret is deprecated and will be removed in a future release. Please use service.secret instead.") + ServiceSecret.Set(ServiceJWTSecret.GetString()) + } + } + if _, err := url.ParseRequestURI(AvatarGravatarBaseURL.GetString()); err != nil { log.Fatalf("Could not parse gravatarbaseurl: %s", err) } diff --git a/pkg/doctor/config.go b/pkg/doctor/config.go index b9114d757..209c6e6a5 100644 --- a/pkg/doctor/config.go +++ b/pkg/doctor/config.go @@ -78,7 +78,7 @@ func checkPublicURL() CheckResult { func checkJWTSecret() CheckResult { // We can't check the actual value, but we can check if it's the default length // which would indicate it was auto-generated - secret := config.ServiceJWTSecret.GetString() + secret := config.ServiceSecret.GetString() // Auto-generated secrets are 64 hex characters (32 bytes) if len(secret) == 64 { diff --git a/pkg/e2etests/integrations.go b/pkg/e2etests/integrations.go index cb1ce6371..6ac8860d6 100644 --- a/pkg/e2etests/integrations.go +++ b/pkg/e2etests/integrations.go @@ -126,7 +126,7 @@ func addUserTokenToContext(t *testing.T, u *user.User, c *echo.Context) { token, err := auth.NewUserJWTAuthtoken(u, "test-session-id") require.NoError(t, err) tken, err := jwt.Parse(token, func(_ *jwt.Token) (interface{}, error) { - return []byte(config.ServiceJWTSecret.GetString()), nil + return []byte(config.ServiceSecret.GetString()), nil }) require.NoError(t, err) c.Set("user", tken) diff --git a/pkg/modules/auth/auth.go b/pkg/modules/auth/auth.go index dc2ba2c78..2b6662031 100644 --- a/pkg/modules/auth/auth.go +++ b/pkg/modules/auth/auth.go @@ -135,7 +135,7 @@ func NewUserJWTAuthtoken(u *user.User, sessionID string) (token string, err erro claims["sid"] = sessionID claims["jti"] = uuid.New().String() - return t.SignedString([]byte(config.ServiceJWTSecret.GetString())) + return t.SignedString([]byte(config.ServiceSecret.GetString())) } // NewLinkShareJWTAuthtoken creates a new jwt token from a link share @@ -156,7 +156,7 @@ func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err erro claims["exp"] = exp // Generate encoded token and send it as response. - return t.SignedString([]byte(config.ServiceJWTSecret.GetString())) + return t.SignedString([]byte(config.ServiceSecret.GetString())) } // GetAuthFromClaims returns a web.Auth object from jwt claims diff --git a/pkg/routes/api_tokens.go b/pkg/routes/api_tokens.go index 7d8ffe35b..31045f28c 100644 --- a/pkg/routes/api_tokens.go +++ b/pkg/routes/api_tokens.go @@ -39,7 +39,7 @@ const ErrCodeInvalidToken = 11 func SetupTokenMiddleware() echo.MiddlewareFunc { return echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(config.ServiceJWTSecret.GetString()), + SigningKey: []byte(config.ServiceSecret.GetString()), Skipper: func(c *echo.Context) bool { authHeader := c.Request().Header.Values("Authorization") if len(authHeader) == 0 { diff --git a/pkg/webtests/integrations.go b/pkg/webtests/integrations.go index b00f3d8a1..0decb1f0a 100644 --- a/pkg/webtests/integrations.go +++ b/pkg/webtests/integrations.go @@ -134,7 +134,7 @@ func addUserTokenToContext(t *testing.T, user *user.User, c *echo.Context) { require.NoError(t, err) // We send the string token through the parsing function to get a valid jwt.Token tken, err := jwt.Parse(token, func(_ *jwt.Token) (interface{}, error) { - return []byte(config.ServiceJWTSecret.GetString()), nil + return []byte(config.ServiceSecret.GetString()), nil }) require.NoError(t, err) c.Set("user", tken) @@ -146,7 +146,7 @@ func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c *echo require.NoError(t, err) // We send the string token through the parsing function to get a valid jwt.Token tken, err := jwt.Parse(token, func(_ *jwt.Token) (interface{}, error) { - return []byte(config.ServiceJWTSecret.GetString()), nil + return []byte(config.ServiceSecret.GetString()), nil }) require.NoError(t, err) c.Set("user", tken) From 1c0513de10280fdbdacd7bbfd2f86a5c7a6ccff2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:54:01 +0000 Subject: [PATCH 019/501] chore(deps): update dev-dependencies --- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 294 ++++++++++++++++++++-------------------- 2 files changed, 149 insertions(+), 149 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 3cc84c67a..4442854b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -125,7 +125,7 @@ "@vueuse/shared": "14.2.1", "autoprefixer": "10.4.27", "browserslist": "4.28.1", - "caniuse-lite": "1.0.30001781", + "caniuse-lite": "1.0.30001782", "csstype": "3.2.3", "esbuild": "0.27.4", "eslint": "9.39.4", @@ -136,7 +136,7 @@ "postcss": "8.5.8", "postcss-easing-gradients": "3.0.1", "postcss-preset-env": "11.2.0", - "rollup": "4.60.0", + "rollup": "4.60.1", "rollup-plugin-visualizer": "6.0.11", "sass-embedded": "1.98.0", "stylelint": "17.6.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 2ca7b9608..53e5f9b1f 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: minimatch: ^10.2.3 - rollup: 4.60.0 + rollup: 4.60.1 basic-ftp: 5.2.0 serialize-javascript: ^7.0.5 flatted: ^3.4.1 @@ -32,7 +32,7 @@ importers: version: 3.1.3(@fortawesome/fontawesome-svg-core@7.1.0)(vue@3.5.27(typescript@5.9.3)) '@intlify/unplugin-vue-i18n': specifier: 11.0.3 - version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.4.2))(rollup@4.60.0)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) + version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.4.2))(rollup@4.60.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) '@kyvg/vue3-notification': specifier: 3.4.2 version: 3.4.2(vue@3.5.27(typescript@5.9.3)) @@ -233,8 +233,8 @@ importers: specifier: 4.28.1 version: 4.28.1 caniuse-lite: - specifier: 1.0.30001781 - version: 1.0.30001781 + specifier: 1.0.30001782 + version: 1.0.30001782 csstype: specifier: 3.2.3 version: 3.2.3 @@ -266,11 +266,11 @@ importers: specifier: 11.2.0 version: 11.2.0(postcss@8.5.8) rollup: - specifier: 4.60.0 - version: 4.60.0 + specifier: 4.60.1 + version: 4.60.1 rollup-plugin-visualizer: specifier: 6.0.11 - version: 6.0.11(rollup@4.60.0) + version: 6.0.11(rollup@4.60.1) sass-embedded: specifier: 1.98.0 version: 1.98.0 @@ -2015,7 +2015,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 '@types/babel__core': ^7.1.9 - rollup: 4.60.0 + rollup: 4.60.1 peerDependenciesMeta: '@types/babel__core': optional: true @@ -2024,7 +2024,7 @@ packages: resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.0 + rollup: 4.60.1 peerDependenciesMeta: rollup: optional: true @@ -2032,13 +2032,13 @@ packages: '@rollup/plugin-replace@2.4.2': resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: - rollup: 4.60.0 + rollup: 4.60.1 '@rollup/plugin-terser@0.4.4': resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.0 + rollup: 4.60.1 peerDependenciesMeta: rollup: optional: true @@ -2047,139 +2047,139 @@ packages: resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} peerDependencies: - rollup: 4.60.0 + rollup: 4.60.1 '@rollup/pluginutils@5.1.3': resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.0 + rollup: 4.60.1 peerDependenciesMeta: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.60.0': - resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.0': - resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.0': - resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.0': - resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.0': - resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.0': - resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.0': - resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.0': - resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.60.0': - resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.0': - resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.60.0': - resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.60.0': - resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.60.0': - resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.60.0': - resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.60.0': - resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.60.0': - resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.60.0': - resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.0': - resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.0': - resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.60.0': - resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.0': - resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.0': - resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.0': - resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.0': - resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.0': - resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} cpu: [x64] os: [win32] @@ -3286,8 +3286,8 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001781: - resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} + caniuse-lite@1.0.30001782: + resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} capture-website@4.2.0: resolution: {integrity: sha512-EmkSn36CXTC8tUsS6aNmvvsdpfVTYYkuRp7U5bV9gcJwcDbqqA5c0Op/iskYPKtDdOkuVp61mjn/LLywX0h7cw==} @@ -5588,15 +5588,15 @@ packages: hasBin: true peerDependencies: rolldown: 1.x || ^1.0.0-beta - rollup: 4.60.0 + rollup: 4.60.1 peerDependenciesMeta: rolldown: optional: true rollup: optional: true - rollup@4.60.0: - resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8398,13 +8398,13 @@ snapshots: '@intlify/shared@11.2.8': {} - '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.4.2))(rollup@4.60.0)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': + '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.4.2))(rollup@4.60.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.4(jiti@2.4.2)) '@intlify/bundle-utils': 11.0.3(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3))) '@intlify/shared': 11.2.2 '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.2)(@vue/compiler-dom@3.5.27)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) - '@rollup/pluginutils': 5.1.3(rollup@4.60.0) + '@rollup/pluginutils': 5.1.3(rollup@4.60.1) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) debug: 4.4.3 @@ -8628,128 +8628,128 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.2': {} - '@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(rollup@4.60.0)': + '@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(rollup@4.60.1)': dependencies: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.25.9 - '@rollup/pluginutils': 3.1.0(rollup@4.60.0) - rollup: 4.60.0 + '@rollup/pluginutils': 3.1.0(rollup@4.60.1) + rollup: 4.60.1 transitivePeerDependencies: - supports-color - '@rollup/plugin-node-resolve@15.2.3(rollup@4.60.0)': + '@rollup/plugin-node-resolve@15.2.3(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.60.0) + '@rollup/pluginutils': 5.1.3(rollup@4.60.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 optionalDependencies: - rollup: 4.60.0 + rollup: 4.60.1 - '@rollup/plugin-replace@2.4.2(rollup@4.60.0)': + '@rollup/plugin-replace@2.4.2(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 3.1.0(rollup@4.60.0) + '@rollup/pluginutils': 3.1.0(rollup@4.60.1) magic-string: 0.25.9 - rollup: 4.60.0 + rollup: 4.60.1 - '@rollup/plugin-terser@0.4.4(rollup@4.60.0)': + '@rollup/plugin-terser@0.4.4(rollup@4.60.1)': dependencies: serialize-javascript: 7.0.5 smob: 1.5.0 terser: 5.31.6 optionalDependencies: - rollup: 4.60.0 + rollup: 4.60.1 - '@rollup/pluginutils@3.1.0(rollup@4.60.0)': + '@rollup/pluginutils@3.1.0(rollup@4.60.1)': dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.3.2 - rollup: 4.60.0 + rollup: 4.60.1 - '@rollup/pluginutils@5.1.3(rollup@4.60.0)': + '@rollup/pluginutils@5.1.3(rollup@4.60.1)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 4.60.0 + rollup: 4.60.1 - '@rollup/rollup-android-arm-eabi@4.60.0': + '@rollup/rollup-android-arm-eabi@4.60.1': optional: true - '@rollup/rollup-android-arm64@4.60.0': + '@rollup/rollup-android-arm64@4.60.1': optional: true - '@rollup/rollup-darwin-arm64@4.60.0': + '@rollup/rollup-darwin-arm64@4.60.1': optional: true - '@rollup/rollup-darwin-x64@4.60.0': + '@rollup/rollup-darwin-x64@4.60.1': optional: true - '@rollup/rollup-freebsd-arm64@4.60.0': + '@rollup/rollup-freebsd-arm64@4.60.1': optional: true - '@rollup/rollup-freebsd-x64@4.60.0': + '@rollup/rollup-freebsd-x64@4.60.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.0': + '@rollup/rollup-linux-arm-musleabihf@4.60.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.0': + '@rollup/rollup-linux-arm64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.0': + '@rollup/rollup-linux-arm64-musl@4.60.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.0': + '@rollup/rollup-linux-loong64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.0': + '@rollup/rollup-linux-loong64-musl@4.60.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.0': + '@rollup/rollup-linux-ppc64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.0': + '@rollup/rollup-linux-ppc64-musl@4.60.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.0': + '@rollup/rollup-linux-riscv64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.0': + '@rollup/rollup-linux-riscv64-musl@4.60.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.0': + '@rollup/rollup-linux-s390x-gnu@4.60.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.0': + '@rollup/rollup-linux-x64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-x64-musl@4.60.0': + '@rollup/rollup-linux-x64-musl@4.60.1': optional: true - '@rollup/rollup-openbsd-x64@4.60.0': + '@rollup/rollup-openbsd-x64@4.60.1': optional: true - '@rollup/rollup-openharmony-arm64@4.60.0': + '@rollup/rollup-openharmony-arm64@4.60.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.0': + '@rollup/rollup-win32-arm64-msvc@4.60.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.0': + '@rollup/rollup-win32-ia32-msvc@4.60.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.0': + '@rollup/rollup-win32-x64-gnu@4.60.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.0': + '@rollup/rollup-win32-x64-msvc@4.60.1': optional: true '@sentry-internal/browser-utils@10.36.0': @@ -9822,7 +9822,7 @@ snapshots: autoprefixer@10.4.27(postcss@8.5.8): dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001781 + caniuse-lite: 1.0.30001782 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.8 @@ -9941,7 +9941,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.4 - caniuse-lite: 1.0.30001781 + caniuse-lite: 1.0.30001782 electron-to-chromium: 1.5.266 node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) @@ -10003,7 +10003,7 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001781: {} + caniuse-lite@1.0.30001782: {} capture-website@4.2.0(typescript@5.9.3): dependencies: @@ -12514,44 +12514,44 @@ snapshots: rfdc@1.4.1: {} - rollup-plugin-visualizer@6.0.11(rollup@4.60.0): + rollup-plugin-visualizer@6.0.11(rollup@4.60.1): dependencies: open: 8.4.2 picomatch: 4.0.4 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.60.0 + rollup: 4.60.1 - rollup@4.60.0: + rollup@4.60.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.0 - '@rollup/rollup-android-arm64': 4.60.0 - '@rollup/rollup-darwin-arm64': 4.60.0 - '@rollup/rollup-darwin-x64': 4.60.0 - '@rollup/rollup-freebsd-arm64': 4.60.0 - '@rollup/rollup-freebsd-x64': 4.60.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 - '@rollup/rollup-linux-arm-musleabihf': 4.60.0 - '@rollup/rollup-linux-arm64-gnu': 4.60.0 - '@rollup/rollup-linux-arm64-musl': 4.60.0 - '@rollup/rollup-linux-loong64-gnu': 4.60.0 - '@rollup/rollup-linux-loong64-musl': 4.60.0 - '@rollup/rollup-linux-ppc64-gnu': 4.60.0 - '@rollup/rollup-linux-ppc64-musl': 4.60.0 - '@rollup/rollup-linux-riscv64-gnu': 4.60.0 - '@rollup/rollup-linux-riscv64-musl': 4.60.0 - '@rollup/rollup-linux-s390x-gnu': 4.60.0 - '@rollup/rollup-linux-x64-gnu': 4.60.0 - '@rollup/rollup-linux-x64-musl': 4.60.0 - '@rollup/rollup-openbsd-x64': 4.60.0 - '@rollup/rollup-openharmony-arm64': 4.60.0 - '@rollup/rollup-win32-arm64-msvc': 4.60.0 - '@rollup/rollup-win32-ia32-msvc': 4.60.0 - '@rollup/rollup-win32-x64-gnu': 4.60.0 - '@rollup/rollup-win32-x64-msvc': 4.60.0 + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -13542,7 +13542,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.8 - rollup: 4.60.0 + rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.0 @@ -13757,10 +13757,10 @@ snapshots: '@babel/core': 7.26.0 '@babel/preset-env': 7.26.0(@babel/core@7.26.0) '@babel/runtime': 7.25.4 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.26.0)(rollup@4.60.0) - '@rollup/plugin-node-resolve': 15.2.3(rollup@4.60.0) - '@rollup/plugin-replace': 2.4.2(rollup@4.60.0) - '@rollup/plugin-terser': 0.4.4(rollup@4.60.0) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.26.0)(rollup@4.60.1) + '@rollup/plugin-node-resolve': 15.2.3(rollup@4.60.1) + '@rollup/plugin-replace': 2.4.2(rollup@4.60.1) + '@rollup/plugin-terser': 0.4.4(rollup@4.60.1) '@surma/rollup-plugin-off-main-thread': 2.2.3 ajv: 8.18.0 common-tags: 1.8.2 @@ -13769,7 +13769,7 @@ snapshots: glob: 11.1.0 lodash: 4.17.23 pretty-bytes: 5.6.0 - rollup: 4.60.0 + rollup: 4.60.1 source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 From b0b7c52b155568e7b7206219d936e64a967eaa4d Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:40 +0100 Subject: [PATCH 020/501] feat: register caldav permission group for API tokens --- pkg/models/api_routes.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/models/api_routes.go b/pkg/models/api_routes.go index 5dd7e92e7..2f52237a7 100644 --- a/pkg/models/api_routes.go +++ b/pkg/models/api_routes.go @@ -29,6 +29,12 @@ var apiTokenRoutes = map[string]APITokenRoute{} func init() { apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutes["caldav"] = APITokenRoute{ + "access": &RouteDetail{ + Path: "/dav/*", + Method: "ANY", + }, + } } type APITokenRoute map[string]*RouteDetail From ebec91b356f05e142c6532e725544f4d34b70e64 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:44 +0100 Subject: [PATCH 021/501] feat: add HasCaldavAccess method to APIToken --- pkg/models/api_tokens.go | 10 ++++++++++ pkg/models/api_tokens_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pkg/models/api_tokens.go b/pkg/models/api_tokens.go index 00ae5b747..6552d14eb 100644 --- a/pkg/models/api_tokens.go +++ b/pkg/models/api_tokens.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/hex" + "slices" "time" "code.vikunja.io/api/pkg/db" @@ -169,6 +170,15 @@ func (t *APIToken) Delete(s *xorm.Session, a web.Auth) (err error) { return err } +// HasCaldavAccess checks whether the token has the caldav access permission. +func (t *APIToken) HasCaldavAccess() bool { + perms, has := t.APIPermissions["caldav"] + if !has { + return false + } + return slices.Contains(perms, "access") +} + // GetTokenFromTokenString returns the full token object from the original token string. func GetTokenFromTokenString(s *xorm.Session, token string) (apiToken *APIToken, err error) { lastEight := token[len(token)-8:] diff --git a/pkg/models/api_tokens_test.go b/pkg/models/api_tokens_test.go index e0ebca8d6..6677c09fa 100644 --- a/pkg/models/api_tokens_test.go +++ b/pkg/models/api_tokens_test.go @@ -95,6 +95,36 @@ func TestAPIToken_Create(t *testing.T) { }) } +func TestAPIToken_HasCaldavAccess(t *testing.T) { + t.Run("has caldav access", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"caldav": {"access"}}, + } + assert.True(t, token.HasCaldavAccess()) + }) + t.Run("no caldav group", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"tasks": {"read_all"}}, + } + assert.False(t, token.HasCaldavAccess()) + }) + t.Run("caldav group but wrong permission", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"caldav": {"read_all"}}, + } + assert.False(t, token.HasCaldavAccess()) + }) + t.Run("caldav access among other permissions", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{ + "tasks": {"read_all", "update"}, + "caldav": {"access"}, + }, + } + assert.True(t, token.HasCaldavAccess()) + }) +} + func TestAPIToken_GetTokenFromTokenString(t *testing.T) { t.Run("valid token", func(t *testing.T) { s := db.NewSession() From 620770592800a984010386be491d4fb6b3f92bcd Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:47 +0100 Subject: [PATCH 022/501] feat: accept API tokens for CalDAV basic auth --- pkg/routes/caldav/auth.go | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pkg/routes/caldav/auth.go b/pkg/routes/caldav/auth.go index 9dd85e50a..1613fb9d1 100644 --- a/pkg/routes/caldav/auth.go +++ b/pkg/routes/caldav/auth.go @@ -18,9 +18,12 @@ package caldav import ( "errors" + "strings" + "time" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/user" "xorm.io/xorm" @@ -28,10 +31,62 @@ import ( "golang.org/x/crypto/bcrypt" ) +func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error) { + apiToken, err := models.GetTokenFromTokenString(s, token) + if err != nil { + if models.IsErrAPITokenInvalid(err) { + log.Debugf("[caldav auth] Invalid API token provided for user %s", username) + return nil, nil + } + return nil, err + } + + if time.Now().After(apiToken.ExpiresAt) { + log.Debugf("[caldav auth] API token %d has expired", apiToken.ID) + return nil, nil + } + + if !apiToken.HasCaldavAccess() { + log.Debugf("[caldav auth] API token %d does not have caldav access permission", apiToken.ID) + return nil, nil + } + + u, err := user.GetUserByID(s, apiToken.OwnerID) + if err != nil { + if user.IsErrUserStatusError(err) { + log.Debugf("[caldav auth] API token %d owner account is disabled or locked", apiToken.ID) + return nil, nil + } + return nil, err + } + + if u.Username != username { + log.Debugf("[caldav auth] API token %d owner %s does not match provided username %s", apiToken.ID, u.Username, username) + return nil, nil + } + + return u, nil +} + func BasicAuth(c *echo.Context, username, password string) (bool, error) { s := db.NewSession() defer s.Close() + // If the password looks like an API token, validate it as one. + // Don't fall through to other auth methods — tk_ prefix is unambiguous. + if strings.HasPrefix(password, models.APITokenPrefix) { + u, err := checkAPIToken(s, username, password) + if err != nil { + log.Errorf("Error during API token auth for caldav: %v", err) + return false, nil + } + if u != nil { + c.Set("userBasicAuth", u) + return true, nil + } + return false, nil + } + credentials := &user.Login{ Username: username, Password: password, From 194bec8b9ff12142ca57ef36fa034218e6f8f2b2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:51 +0100 Subject: [PATCH 023/501] test: add integration tests for CalDAV API token auth --- pkg/db/fixtures/api_tokens.yml | 20 ++++++++++++++++++ pkg/webtests/caldav_test.go | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/pkg/db/fixtures/api_tokens.yml b/pkg/db/fixtures/api_tokens.yml index b7a225616..2ff417bff 100644 --- a/pkg/db/fixtures/api_tokens.yml +++ b/pkg/db/fixtures/api_tokens.yml @@ -48,3 +48,23 @@ owner_id: 18 created: 2023-09-01 07:00:00 # token in plaintext is tk_locked_user_test_token_0000000012345678 +- id: 6 + title: 'caldav access token for user 15' + token_salt: cDvTk9sR2m + token_hash: 41f673b144dd743df03de7fb3770766d09f0ac11619cbcca1849310bf71093b872258d5f3b5fc0308ac23910c5570e602b25 + token_last_eight: aabbccdd + permissions: '{"caldav":["access"]}' + expires_at: 2099-01-01 00:00:00 + owner_id: 15 + created: 2024-01-01 00:00:00 + # token in plaintext is tk_caldav_api_token_test_00000000aabbccdd +- id: 7 + title: 'non-caldav token for user 15' + token_salt: xY7mNp3qRs + token_hash: 844f04afac4479a690b303dbc96795f83526aba0dce11f917e918699542e7ae53f869a9d6e03e147e12350bdf1a710e09cc9 + token_last_eight: 5678efab + permissions: '{"tasks":["read_all"]}' + expires_at: 2099-01-01 00:00:00 + owner_id: 15 + created: 2024-01-01 00:00:00 + # token in plaintext is tk_nocaldav_token_test_000000005678efab diff --git a/pkg/webtests/caldav_test.go b/pkg/webtests/caldav_test.go index fb4eb89e5..3157539dc 100644 --- a/pkg/webtests/caldav_test.go +++ b/pkg/webtests/caldav_test.go @@ -910,3 +910,41 @@ func TestCaldavDisabledUserRejected(t *testing.T) { assert.False(t, result, "locked user should not be able to authenticate via CalDAV") }) } + +func TestCaldavAPITokenAuth(t *testing.T) { + t.Run("API token with caldav permission succeeds", func(t *testing.T) { + e, _ := setupTestEnv() + c, _ := createRequest(e, http.MethodGet, "", nil, nil) + + // API token fixture id 6: owner_id=15, permissions={"caldav":["access"]} + result, err := caldav.BasicAuth(c, testuser15.Username, "tk_caldav_api_token_test_00000000aabbccdd") + require.NoError(t, err) + assert.True(t, result, "API token with caldav permission should authenticate") + }) + t.Run("API token without caldav permission rejected", func(t *testing.T) { + e, _ := setupTestEnv() + c, _ := createRequest(e, http.MethodGet, "", nil, nil) + + // API token fixture id 7: owner_id=15, permissions={"tasks":["read_all"]} + result, err := caldav.BasicAuth(c, testuser15.Username, "tk_nocaldav_token_test_000000005678efab") + require.NoError(t, err) + assert.False(t, result, "API token without caldav permission should be rejected") + }) + t.Run("API token with wrong username rejected", func(t *testing.T) { + e, _ := setupTestEnv() + c, _ := createRequest(e, http.MethodGet, "", nil, nil) + + // Token belongs to user15 but we provide user1's username + result, err := caldav.BasicAuth(c, testuser1.Username, "tk_caldav_api_token_test_00000000aabbccdd") + require.NoError(t, err) + assert.False(t, result, "API token with mismatched username should be rejected") + }) + t.Run("invalid API token rejected", func(t *testing.T) { + e, _ := setupTestEnv() + c, _ := createRequest(e, http.MethodGet, "", nil, nil) + + result, err := caldav.BasicAuth(c, testuser15.Username, "tk_this_is_totally_not_a_valid_token_at_all") + require.NoError(t, err) + assert.False(t, result, "invalid API token should be rejected") + }) +} From 390957b3f5d7790d0ecbe3dde68b388632da7118 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:55 +0100 Subject: [PATCH 024/501] test: verify caldav permission group appears in /routes --- pkg/webtests/api_tokens_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/webtests/api_tokens_test.go b/pkg/webtests/api_tokens_test.go index 8434cd488..e78d09ef9 100644 --- a/pkg/webtests/api_tokens_test.go +++ b/pkg/webtests/api_tokens_test.go @@ -31,6 +31,27 @@ import ( "github.com/stretchr/testify/require" ) +func TestAPITokenRoutesIncludesCaldav(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + + s := db.NewSession() + defer s.Close() + u, err := user.GetUserByID(s, 1) + require.NoError(t, err) + jwt, err := auth.NewUserJWTAuthtoken(u, "test-session-id") + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/routes", nil) + req.Header.Set(echo.HeaderAuthorization, "Bearer "+jwt) + res := httptest.NewRecorder() + e.ServeHTTP(res, req) + + assert.Equal(t, http.StatusOK, res.Code) + assert.Contains(t, res.Body.String(), `"caldav"`) + assert.Contains(t, res.Body.String(), `"access"`) +} + func TestAPIToken(t *testing.T) { t.Run("valid token", func(t *testing.T) { e, err := setupTestEnv() From c2cfcb4684774eae082072ee335f8481a0b2cce2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:31:59 +0100 Subject: [PATCH 025/501] feat: add API token hint to CalDAV settings page --- frontend/src/i18n/lang/en.json | 3 ++- frontend/src/views/user/settings/Caldav.vue | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 28e185a4a..4bd8c723e 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -156,7 +156,8 @@ "tokenCreated": "Here is your new token: {token}", "wontSeeItAgain": "Write it down or save it securely — you will not be able to see it again.", "mustUseToken": "You need to create a CalDAV token to use CalDAV with any third-party client. Enter the token in the password field of your client.", - "usernameIs": "Your username for CalDAV is: {0}" + "usernameIs": "Your username for CalDAV is: {0}", + "apiTokenHint": "You can also use an API token with CalDAV permission. Create one in {link}." }, "avatar": { "title": "Avatar", diff --git a/frontend/src/views/user/settings/Caldav.vue b/frontend/src/views/user/settings/Caldav.vue index 0525b9867..3fc1b2d15 100644 --- a/frontend/src/views/user/settings/Caldav.vue +++ b/frontend/src/views/user/settings/Caldav.vue @@ -36,6 +36,19 @@

+

+ + + +

+ Date: Mon, 30 Mar 2026 14:00:56 +0200 Subject: [PATCH 026/501] refactor: extract shared API token validation into ValidateTokenAndGetOwner --- pkg/models/api_tokens.go | 30 ++++++++++++++++++++++++++++++ pkg/routes/api_tokens.go | 18 +++--------------- pkg/routes/caldav/auth.go | 20 ++------------------ 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/pkg/models/api_tokens.go b/pkg/models/api_tokens.go index 6552d14eb..b432b235c 100644 --- a/pkg/models/api_tokens.go +++ b/pkg/models/api_tokens.go @@ -24,6 +24,7 @@ import ( "time" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/web" @@ -198,3 +199,32 @@ func GetTokenFromTokenString(s *xorm.Session, token string) (apiToken *APIToken, return nil, &ErrAPITokenInvalid{} } + +// ValidateTokenAndGetOwner looks up a raw token string, checks it is not expired, +// and returns both the APIToken and its owner. Callers are responsible for checking +// permissions on the returned token (e.g. CanDoAPIRoute or HasCaldavAccess). +// Returns (nil, nil, nil) if the token is invalid or expired, or if the owner +// account is disabled/locked. +func ValidateTokenAndGetOwner(s *xorm.Session, rawToken string) (*APIToken, *user.User, error) { + apiToken, err := GetTokenFromTokenString(s, rawToken) + if err != nil { + if IsErrAPITokenInvalid(err) { + return nil, nil, nil + } + return nil, nil, err + } + + if time.Now().After(apiToken.ExpiresAt) { + return nil, nil, nil + } + + u, err := user.GetUserByID(s, apiToken.OwnerID) + if err != nil { + if user.IsErrUserStatusError(err) { + return nil, nil, nil + } + return nil, nil, err + } + + return apiToken, u, nil +} diff --git a/pkg/routes/api_tokens.go b/pkg/routes/api_tokens.go index 31045f28c..cc6c2f546 100644 --- a/pkg/routes/api_tokens.go +++ b/pkg/routes/api_tokens.go @@ -19,13 +19,11 @@ package routes import ( "net/http" "strings" - "time" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" echojwt "github.com/labstack/echo-jwt/v5" @@ -76,13 +74,12 @@ func SetupTokenMiddleware() echo.MiddlewareFunc { func checkAPITokenAndPutItInContext(tokenHeaderValue string, c *echo.Context) error { s := db.NewSession() defer s.Close() - token, err := models.GetTokenFromTokenString(s, strings.TrimPrefix(tokenHeaderValue, "Bearer ")) + + token, u, err := models.ValidateTokenAndGetOwner(s, strings.TrimPrefix(tokenHeaderValue, "Bearer ")) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Internal Server Error").Wrap(err) } - - if time.Now().After(token.ExpiresAt) { - log.Debugf("[auth] Tried authenticating with token %d but it expired on %s", token.ID, token.ExpiresAt.String()) + if token == nil || u == nil { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } @@ -91,15 +88,6 @@ func checkAPITokenAndPutItInContext(tokenHeaderValue string, c *echo.Context) er return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } - u, err := user.GetUserByID(s, token.OwnerID) - if user.IsErrUserStatusError(err) { - log.Debugf("[auth] Tried authenticating with token %d but the owner's account is disabled or locked", token.ID) - return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") - } - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Internal Server Error").Wrap(err) - } - c.Set("api_token", token) c.Set("api_user", u) diff --git a/pkg/routes/caldav/auth.go b/pkg/routes/caldav/auth.go index 1613fb9d1..a2b617809 100644 --- a/pkg/routes/caldav/auth.go +++ b/pkg/routes/caldav/auth.go @@ -19,7 +19,6 @@ package caldav import ( "errors" "strings" - "time" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" @@ -32,17 +31,11 @@ import ( ) func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error) { - apiToken, err := models.GetTokenFromTokenString(s, token) + apiToken, u, err := models.ValidateTokenAndGetOwner(s, token) if err != nil { - if models.IsErrAPITokenInvalid(err) { - log.Debugf("[caldav auth] Invalid API token provided for user %s", username) - return nil, nil - } return nil, err } - - if time.Now().After(apiToken.ExpiresAt) { - log.Debugf("[caldav auth] API token %d has expired", apiToken.ID) + if apiToken == nil || u == nil { return nil, nil } @@ -51,15 +44,6 @@ func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error) return nil, nil } - u, err := user.GetUserByID(s, apiToken.OwnerID) - if err != nil { - if user.IsErrUserStatusError(err) { - log.Debugf("[caldav auth] API token %d owner account is disabled or locked", apiToken.ID) - return nil, nil - } - return nil, err - } - if u.Username != username { log.Debugf("[caldav auth] API token %d owner %s does not match provided username %s", apiToken.ID, u.Username, username) return nil, nil From d3f9bb4ee852a6622c113928a238707e3f154745 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:03 +0100 Subject: [PATCH 027/501] feat: add i18n keys for API token expiry notifications --- pkg/i18n/lang/en.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/i18n/lang/en.json b/pkg/i18n/lang/en.json index ec79274bc..eaf22b241 100644 --- a/pkg/i18n/lang/en.json +++ b/pkg/i18n/lang/en.json @@ -133,6 +133,19 @@ "working_on_it": "We've got the error message on our radar and are on it to get it sorted out soon." } }, + "api_token": { + "expiring": { + "week": { + "subject": "Your API token \"%[1]s\" expires in 7 days", + "message": "Your API token \"%[1]s\" will expire on %[2]s. If you still need it, please create a new token before it expires." + }, + "day": { + "subject": "Your API token \"%[1]s\" expires tomorrow", + "message": "Your API token \"%[1]s\" will expire on %[2]s. If you still need it, please create a new token before it expires." + }, + "action": "Manage API Tokens" + } + }, "common": { "have_nice_day": "Have a nice day!", "copy_url": "If the button above doesn't work, copy the url below and paste it in your browser's address bar:", From 8ea0dd1610b456b507351f25ae0139340d070447 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:04 +0100 Subject: [PATCH 028/501] feat: add API token expiry notification types --- pkg/models/api_tokens_expiry_notification.go | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 pkg/models/api_tokens_expiry_notification.go diff --git a/pkg/models/api_tokens_expiry_notification.go b/pkg/models/api_tokens_expiry_notification.go new file mode 100644 index 000000000..656ffe279 --- /dev/null +++ b/pkg/models/api_tokens_expiry_notification.go @@ -0,0 +1,78 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/i18n" + "code.vikunja.io/api/pkg/notifications" + "code.vikunja.io/api/pkg/user" +) + +// APITokenExpiringWeekNotification is sent 7 days before an API token expires. +type APITokenExpiringWeekNotification struct { + User *user.User `json:"user"` + Token *APIToken `json:"api_token"` +} + +func (n *APITokenExpiringWeekNotification) ToMail(lang string) *notifications.Mail { + return notifications.NewMail(). + Subject(i18n.T(lang, "notifications.api_token.expiring.week.subject", n.Token.Title)). + Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). + Line(i18n.T(lang, "notifications.api_token.expiring.week.message", n.Token.Title, n.Token.ExpiresAt.Format("January 2, 2006"))). + Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). + Line(i18n.T(lang, "notifications.common.have_nice_day")) +} + +func (n *APITokenExpiringWeekNotification) ToDB() any { + return n +} + +func (n *APITokenExpiringWeekNotification) Name() string { + return "api_token.expiring.week" +} + +func (n *APITokenExpiringWeekNotification) SubjectID() int64 { + return n.Token.ID +} + +// APITokenExpiringDayNotification is sent 1 day before an API token expires. +type APITokenExpiringDayNotification struct { + User *user.User `json:"user"` + Token *APIToken `json:"api_token"` +} + +func (n *APITokenExpiringDayNotification) ToMail(lang string) *notifications.Mail { + return notifications.NewMail(). + Subject(i18n.T(lang, "notifications.api_token.expiring.day.subject", n.Token.Title)). + Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). + Line(i18n.T(lang, "notifications.api_token.expiring.day.message", n.Token.Title, n.Token.ExpiresAt.Format("January 2, 2006"))). + Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). + Line(i18n.T(lang, "notifications.common.have_nice_day")) +} + +func (n *APITokenExpiringDayNotification) ToDB() any { + return n +} + +func (n *APITokenExpiringDayNotification) Name() string { + return "api_token.expiring.day" +} + +func (n *APITokenExpiringDayNotification) SubjectID() int64 { + return n.Token.ID +} From f30858403385cf7aec30bb7e7894488ec88067cb Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:05 +0100 Subject: [PATCH 029/501] feat: add cron job for API token expiry notifications --- pkg/models/api_tokens_expiry_cron.go | 131 +++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 pkg/models/api_tokens_expiry_cron.go diff --git a/pkg/models/api_tokens_expiry_cron.go b/pkg/models/api_tokens_expiry_cron.go new file mode 100644 index 000000000..218a98f02 --- /dev/null +++ b/pkg/models/api_tokens_expiry_cron.go @@ -0,0 +1,131 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "time" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/cron" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/notifications" + "code.vikunja.io/api/pkg/user" + + "xorm.io/builder" + "xorm.io/xorm" +) + +// RegisterAPITokenExpiryCheckCron registers the cron job that checks for +// expiring API tokens and notifies their owners. +func RegisterAPITokenExpiryCheckCron() { + if !config.MailerEnabled.GetBool() { + return + } + + err := cron.Schedule("0 * * * *", checkForExpiringAPITokens) + if err != nil { + log.Fatalf("Could not register API token expiry check cron: %s", err) + } +} + +func checkForExpiringAPITokens() { + checkForExpiringAPITokensAt(time.Now()) +} + +func checkForExpiringAPITokensAt(now time.Time) { + const logPrefix = "[API Token Expiry Check] " + + oneDay := now.Add(24 * time.Hour) + sevenDays := now.Add(7 * 24 * time.Hour) + + s := db.NewSession() + defer s.Close() + + // Find all tokens expiring within the next 7 days that haven't expired yet + var tokens []*APIToken + err := s.Where( + builder.Gt{"expires_at": now}, + ).And( + builder.Lte{"expires_at": sevenDays}, + ).Find(&tokens) + if err != nil { + log.Errorf(logPrefix+"Error getting expiring tokens: %s", err) + return + } + + if len(tokens) == 0 { + return + } + + log.Debugf(logPrefix+"Found %d tokens expiring within 7 days", len(tokens)) + + // Collect unique owner IDs and fetch users + ownerIDs := make([]int64, 0, len(tokens)) + for _, token := range tokens { + ownerIDs = append(ownerIDs, token.OwnerID) + } + + users, err := user.GetUsersByIDs(s, ownerIDs) + if err != nil { + log.Errorf(logPrefix+"Error getting token owners: %s", err) + return + } + + for _, token := range tokens { + u, exists := users[token.OwnerID] + if !exists { + continue + } + + // Determine which thresholds apply + expiresWithinOneDay := token.ExpiresAt.Before(oneDay) || token.ExpiresAt.Equal(oneDay) + + if expiresWithinOneDay { + if err := sendTokenExpiryNotificationIfNew(s, u, token, &APITokenExpiringDayNotification{ + User: u, + Token: token, + }); err != nil { + log.Errorf(logPrefix+"Error sending 1-day notification for token %d: %s", token.ID, err) + } + } + + // Always check the 7-day notification (token is within 7 days by the query) + if err := sendTokenExpiryNotificationIfNew(s, u, token, &APITokenExpiringWeekNotification{ + User: u, + Token: token, + }); err != nil { + log.Errorf(logPrefix+"Error sending 7-day notification for token %d: %s", token.ID, err) + } + } +} + +// sendTokenExpiryNotificationIfNew checks whether a notification with the same +// name and subject (token ID) has already been sent for this user. If not, it +// sends the notification (both email and DB). +func sendTokenExpiryNotificationIfNew(s *xorm.Session, u *user.User, _ *APIToken, n notifications.NotificationWithSubject) error { + existing, err := notifications.GetNotificationsForNameAndUser(s, u.ID, n.Name(), n.SubjectID()) + if err != nil { + return err + } + + if len(existing) > 0 { + return nil + } + + return notifications.Notify(u, n, s) +} From 04f94a5801410a65b2d424c36ac9afa19d37b946 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:09 +0100 Subject: [PATCH 030/501] feat: register API token expiry check cron on startup --- pkg/initialize/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/initialize/init.go b/pkg/initialize/init.go index 0ec1c9fd7..a614da182 100644 --- a/pkg/initialize/init.go +++ b/pkg/initialize/init.go @@ -127,6 +127,7 @@ func FullInit() { user.RegisterDeletionNotificationCron() openid.CleanupSavedOpenIDProviders() openid.RegisterEmptyOpenIDTeamCleanupCron() + models.RegisterAPITokenExpiryCheckCron() // Start processing events go func() { From 6dc46c1898dce728f0b16ab8156026dc99d20b4e Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:09 +0100 Subject: [PATCH 031/501] feat: add AssertNotSent helper to notification testing --- pkg/notifications/testing.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/notifications/testing.go b/pkg/notifications/testing.go index bdc8be334..2fd301f8f 100644 --- a/pkg/notifications/testing.go +++ b/pkg/notifications/testing.go @@ -44,3 +44,16 @@ func AssertSent(t *testing.T, n Notification) { assert.True(t, found, "Failed to assert "+n.Name()+" has been sent.") } + +// AssertNotSent asserts a notification has not been sent +func AssertNotSent(t *testing.T, n Notification) { + var found bool + for _, testNotification := range sentTestNotifications { + if n.Name() == testNotification.Name() { + found = true + break + } + } + + assert.False(t, found, "Expected "+n.Name()+" to not have been sent, but it was.") +} From 6b225bb0bae1bda574c89c5fb0f597a8a112666a Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 11:19:10 +0100 Subject: [PATCH 032/501] test: add tests for API token expiry notifications and cron --- pkg/i18n/lang/en.json | 2 +- pkg/models/api_tokens_expiry_cron.go | 24 ++- pkg/models/api_tokens_expiry_cron_test.go | 142 ++++++++++++++++++ pkg/models/api_tokens_expiry_notification.go | 4 +- .../api_tokens_expiry_notification_test.go | 74 +++++++++ pkg/notifications/testing.go | 7 + 6 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 pkg/models/api_tokens_expiry_cron_test.go create mode 100644 pkg/models/api_tokens_expiry_notification_test.go diff --git a/pkg/i18n/lang/en.json b/pkg/i18n/lang/en.json index eaf22b241..9cf69f1c6 100644 --- a/pkg/i18n/lang/en.json +++ b/pkg/i18n/lang/en.json @@ -136,7 +136,7 @@ "api_token": { "expiring": { "week": { - "subject": "Your API token \"%[1]s\" expires in 7 days", + "subject": "Your API token \"%[1]s\" expires soon", "message": "Your API token \"%[1]s\" will expire on %[2]s. If you still need it, please create a new token before it expires." }, "day": { diff --git a/pkg/models/api_tokens_expiry_cron.go b/pkg/models/api_tokens_expiry_cron.go index 218a98f02..25090faaa 100644 --- a/pkg/models/api_tokens_expiry_cron.go +++ b/pkg/models/api_tokens_expiry_cron.go @@ -37,16 +37,12 @@ func RegisterAPITokenExpiryCheckCron() { return } - err := cron.Schedule("0 * * * *", checkForExpiringAPITokens) + err := cron.Schedule("0 * * * *", func() { checkForExpiringAPITokensAt(time.Now()) }) if err != nil { log.Fatalf("Could not register API token expiry check cron: %s", err) } } -func checkForExpiringAPITokens() { - checkForExpiringAPITokensAt(time.Now()) -} - func checkForExpiringAPITokensAt(now time.Time) { const logPrefix = "[API Token Expiry Check] " @@ -92,32 +88,34 @@ func checkForExpiringAPITokensAt(now time.Time) { continue } - // Determine which thresholds apply - expiresWithinOneDay := token.ExpiresAt.Before(oneDay) || token.ExpiresAt.Equal(oneDay) - - if expiresWithinOneDay { - if err := sendTokenExpiryNotificationIfNew(s, u, token, &APITokenExpiringDayNotification{ + // Send only the most urgent notification: 1-day if within 24h, otherwise 7-day + if token.ExpiresAt.Before(oneDay) || token.ExpiresAt.Equal(oneDay) { + if err := sendTokenExpiryNotificationIfNew(s, u, &APITokenExpiringDayNotification{ User: u, Token: token, }); err != nil { log.Errorf(logPrefix+"Error sending 1-day notification for token %d: %s", token.ID, err) } + continue } - // Always check the 7-day notification (token is within 7 days by the query) - if err := sendTokenExpiryNotificationIfNew(s, u, token, &APITokenExpiringWeekNotification{ + if err := sendTokenExpiryNotificationIfNew(s, u, &APITokenExpiringWeekNotification{ User: u, Token: token, }); err != nil { log.Errorf(logPrefix+"Error sending 7-day notification for token %d: %s", token.ID, err) } } + + if err := s.Commit(); err != nil { + log.Errorf(logPrefix+"Error committing session: %s", err) + } } // sendTokenExpiryNotificationIfNew checks whether a notification with the same // name and subject (token ID) has already been sent for this user. If not, it // sends the notification (both email and DB). -func sendTokenExpiryNotificationIfNew(s *xorm.Session, u *user.User, _ *APIToken, n notifications.NotificationWithSubject) error { +func sendTokenExpiryNotificationIfNew(s *xorm.Session, u *user.User, n notifications.NotificationWithSubject) error { existing, err := notifications.GetNotificationsForNameAndUser(s, u.ID, n.Name(), n.SubjectID()) if err != nil { return err diff --git a/pkg/models/api_tokens_expiry_cron_test.go b/pkg/models/api_tokens_expiry_cron_test.go new file mode 100644 index 000000000..3c0ce6063 --- /dev/null +++ b/pkg/models/api_tokens_expiry_cron_test.go @@ -0,0 +1,142 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "testing" + "time" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/notifications" + + "github.com/stretchr/testify/require" +) + +func TestCheckForExpiringAPITokens(t *testing.T) { + t.Run("sends 7-day notification", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + notifications.Fake() + t.Cleanup(notifications.Unfake) + + now := time.Now() + s := db.NewSession() + defer s.Close() + + token := &APIToken{ + Title: "Test 7-day token", + TokenSalt: "salt1", + TokenHash: "uniquehash7day", + TokenLastEight: "test1234", + APIPermissions: APIPermissions{"tasks": {"read"}}, + ExpiresAt: now.Add(6 * 24 * time.Hour), + OwnerID: 1, + } + _, err := s.Insert(token) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + checkForExpiringAPITokensAt(now) + + notifications.AssertSent(t, &APITokenExpiringWeekNotification{}) + }) + + t.Run("sends only 1-day notification for token expiring within 24h", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + notifications.Fake() + t.Cleanup(notifications.Unfake) + + now := time.Now() + s := db.NewSession() + defer s.Close() + + token := &APIToken{ + Title: "Test 1-day token", + TokenSalt: "salt2", + TokenHash: "uniquehash1day", + TokenLastEight: "test5678", + APIPermissions: APIPermissions{"tasks": {"read"}}, + ExpiresAt: now.Add(20 * time.Hour), + OwnerID: 1, + } + _, err := s.Insert(token) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + checkForExpiringAPITokensAt(now) + + notifications.AssertSent(t, &APITokenExpiringDayNotification{}) + notifications.AssertNotSent(t, &APITokenExpiringWeekNotification{}) + }) + + t.Run("does not send for tokens expiring in 30 days", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + notifications.Fake() + t.Cleanup(notifications.Unfake) + + now := time.Now() + s := db.NewSession() + defer s.Close() + + token := &APIToken{ + Title: "Far future token", + TokenSalt: "salt3", + TokenHash: "uniquehash30day", + TokenLastEight: "test9012", + APIPermissions: APIPermissions{"tasks": {"read"}}, + ExpiresAt: now.Add(30 * 24 * time.Hour), + OwnerID: 1, + } + _, err := s.Insert(token) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + checkForExpiringAPITokensAt(now) + + // The existing fixture tokens expire in 2099, so no notifications should be sent + // for our 30-day token either + notifications.AssertNotSent(t, &APITokenExpiringWeekNotification{}) + notifications.AssertNotSent(t, &APITokenExpiringDayNotification{}) + }) + + t.Run("does not send for already expired tokens", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + notifications.Fake() + t.Cleanup(notifications.Unfake) + + now := time.Now() + s := db.NewSession() + defer s.Close() + + token := &APIToken{ + Title: "Expired token", + TokenSalt: "salt4", + TokenHash: "uniquehashexpired", + TokenLastEight: "testexp1", + APIPermissions: APIPermissions{"tasks": {"read"}}, + ExpiresAt: now.Add(-24 * time.Hour), + OwnerID: 1, + } + _, err := s.Insert(token) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + checkForExpiringAPITokensAt(now) + + notifications.AssertNotSent(t, &APITokenExpiringWeekNotification{}) + notifications.AssertNotSent(t, &APITokenExpiringDayNotification{}) + }) +} diff --git a/pkg/models/api_tokens_expiry_notification.go b/pkg/models/api_tokens_expiry_notification.go index 656ffe279..7ee60a11f 100644 --- a/pkg/models/api_tokens_expiry_notification.go +++ b/pkg/models/api_tokens_expiry_notification.go @@ -33,7 +33,7 @@ func (n *APITokenExpiringWeekNotification) ToMail(lang string) *notifications.Ma return notifications.NewMail(). Subject(i18n.T(lang, "notifications.api_token.expiring.week.subject", n.Token.Title)). Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). - Line(i18n.T(lang, "notifications.api_token.expiring.week.message", n.Token.Title, n.Token.ExpiresAt.Format("January 2, 2006"))). + Line(i18n.T(lang, "notifications.api_token.expiring.week.message", n.Token.Title, n.Token.ExpiresAt.Format("2006-01-02"))). Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). Line(i18n.T(lang, "notifications.common.have_nice_day")) } @@ -60,7 +60,7 @@ func (n *APITokenExpiringDayNotification) ToMail(lang string) *notifications.Mai return notifications.NewMail(). Subject(i18n.T(lang, "notifications.api_token.expiring.day.subject", n.Token.Title)). Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). - Line(i18n.T(lang, "notifications.api_token.expiring.day.message", n.Token.Title, n.Token.ExpiresAt.Format("January 2, 2006"))). + Line(i18n.T(lang, "notifications.api_token.expiring.day.message", n.Token.Title, n.Token.ExpiresAt.Format("2006-01-02"))). Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). Line(i18n.T(lang, "notifications.common.have_nice_day")) } diff --git a/pkg/models/api_tokens_expiry_notification_test.go b/pkg/models/api_tokens_expiry_notification_test.go new file mode 100644 index 000000000..0f7327771 --- /dev/null +++ b/pkg/models/api_tokens_expiry_notification_test.go @@ -0,0 +1,74 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "testing" + "time" + + "code.vikunja.io/api/pkg/user" + + "github.com/stretchr/testify/assert" +) + +func TestAPITokenExpiringWeekNotification(t *testing.T) { + u := &user.User{ID: 1, Name: "Test User"} + token := &APIToken{ID: 42, Title: "My Token", ExpiresAt: time.Now().Add(7 * 24 * time.Hour)} + + n := &APITokenExpiringWeekNotification{User: u, Token: token} + + t.Run("Name", func(t *testing.T) { + assert.Equal(t, "api_token.expiring.week", n.Name()) + }) + + t.Run("SubjectID", func(t *testing.T) { + assert.Equal(t, int64(42), n.SubjectID()) + }) + + t.Run("ToDB", func(t *testing.T) { + assert.NotNil(t, n.ToDB()) + }) + + t.Run("ToMail", func(t *testing.T) { + mail := n.ToMail("en") + assert.NotNil(t, mail) + }) +} + +func TestAPITokenExpiringDayNotification(t *testing.T) { + u := &user.User{ID: 1, Name: "Test User"} + token := &APIToken{ID: 99, Title: "CI Token", ExpiresAt: time.Now().Add(24 * time.Hour)} + + n := &APITokenExpiringDayNotification{User: u, Token: token} + + t.Run("Name", func(t *testing.T) { + assert.Equal(t, "api_token.expiring.day", n.Name()) + }) + + t.Run("SubjectID", func(t *testing.T) { + assert.Equal(t, int64(99), n.SubjectID()) + }) + + t.Run("ToDB", func(t *testing.T) { + assert.NotNil(t, n.ToDB()) + }) + + t.Run("ToMail", func(t *testing.T) { + mail := n.ToMail("en") + assert.NotNil(t, mail) + }) +} diff --git a/pkg/notifications/testing.go b/pkg/notifications/testing.go index 2fd301f8f..59826278b 100644 --- a/pkg/notifications/testing.go +++ b/pkg/notifications/testing.go @@ -32,6 +32,13 @@ func Fake() { sentTestNotifications = nil } +// Unfake disables test mode so that subsequent calls to Notify write to the +// database again. Call this (or use t.Cleanup) after tests that use Fake(). +func Unfake() { + isUnderTest = false + sentTestNotifications = nil +} + // AssertSent asserts a notification has been sent func AssertSent(t *testing.T, n Notification) { var found bool From b2ddd2753c030f17d1629144e2a0a3f5cf9e7d9a Mon Sep 17 00:00:00 2001 From: Jacek Galowicz Date: Thu, 26 Mar 2026 12:08:18 +0100 Subject: [PATCH 033/501] config: Expand environment variables in some.config.value.path.file inputs for better secret management --- pkg/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 764f33196..f57248242 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -515,7 +515,7 @@ func GetConfigValueFromFile(configKey string) string { if !strings.HasSuffix(configKey, ".file") { configKey += ".file" } - var valuePath = viper.GetString(configKey) + var valuePath = os.ExpandEnv(viper.GetString(configKey)) if valuePath == "" { return "" } From 111090d12c7319ab7124548f33c1cff013a36ae3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 30 Mar 2026 16:51:05 +0200 Subject: [PATCH 034/501] refactor: use embed fs for redoc UI and update to latest version Move the redoc HTML template and JavaScript bundle out of the Go const in docs.go into separate files under pkg/routes/api/v1/redoc/, using Go's embed directive. Update redoc.standalone.js to the latest version. The JS is now served on a separate route (/api/v1/docs/redoc.standalone.js) to keep the HTML and JS cleanly separated. --- pkg/routes/api/v1/docs.go | 1841 +------------------ pkg/routes/api/v1/redoc/redoc.html | 14 + pkg/routes/api/v1/redoc/redoc.standalone.js | 1832 ++++++++++++++++++ pkg/routes/routes.go | 2 + 4 files changed, 1859 insertions(+), 1830 deletions(-) create mode 100644 pkg/routes/api/v1/redoc/redoc.html create mode 100644 pkg/routes/api/v1/redoc/redoc.standalone.js diff --git a/pkg/routes/api/v1/docs.go b/pkg/routes/api/v1/docs.go index b1ce14567..47e54a469 100644 --- a/pkg/routes/api/v1/docs.go +++ b/pkg/routes/api/v1/docs.go @@ -1,5 +1,3 @@ -//lint:file-ignore ST1018 The const below is not ours - // Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // @@ -19,6 +17,7 @@ package v1 import ( + _ "embed" "net/http" "code.vikunja.io/api/pkg/log" @@ -28,6 +27,12 @@ import ( "github.com/swaggo/swag" ) +//go:embed redoc/redoc.html +var redocHTML string + +//go:embed redoc/redoc.standalone.js +var redocJS []byte + // DocsJSON serves swagger doc json specs func DocsJSON(c *echo.Context) error { @@ -42,1834 +47,10 @@ func DocsJSON(c *echo.Context) error { // RedocUI serves everything needed to provide the redoc ui func RedocUI(c *echo.Context) error { - return c.HTML(http.StatusOK, RedocUITemplate) + return c.HTML(http.StatusOK, redocHTML) } -// RedocUITemplate contains the html + js needed for redoc ui -const RedocUITemplate = ` - - - Vikunja API documentation - - - - - - - - - -` diff --git a/pkg/routes/api/v1/redoc/redoc.html b/pkg/routes/api/v1/redoc/redoc.html new file mode 100644 index 000000000..4ee37f296 --- /dev/null +++ b/pkg/routes/api/v1/redoc/redoc.html @@ -0,0 +1,14 @@ + + + + Vikunja API documentation + + + + + + + + + + diff --git a/pkg/routes/api/v1/redoc/redoc.standalone.js b/pkg/routes/api/v1/redoc/redoc.standalone.js new file mode 100644 index 000000000..f07363773 --- /dev/null +++ b/pkg/routes/api/v1/redoc/redoc.standalone.js @@ -0,0 +1,1832 @@ +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):"function"==typeof define&&define.amd?define(["null"],t):"object"==typeof exports?exports.Redoc=t(require("null")):e.Redoc=t(e.null)}(this,(function(e){return function(){var t={854:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.mapTypeToComponent=t.bundleDocument=t.bundleFromString=t.bundle=t.OasVersion=void 0;const i=n(8142),o=n(2928),s=n(2161),a=n(1990),l=n(5735),c=n(3101),u=n(3873),p=n(2900),d=n(3416),f=n(8209),h=n(4125),m=n(474),g=n(4335);var y;function b(e){return r(this,void 0,void 0,(function*(){const{document:t,config:n,customTypes:r,externalRefResolver:i,dereference:u=!1,skipRedoclyRegistryRefs:d=!1,removeUnusedComponents:f=!1,keepUrlRefs:h=!1}=e,y=(0,c.detectSpec)(t.parsed),b=(0,c.getMajorSpecVersion)(y),v=n.getRulesForOasVersion(b),w=(0,a.normalizeTypes)(n.extendTypes(null!=r?r:(0,c.getTypes)(y),y),n),k=(0,p.initRules)(v,n,"preprocessors",y),S=(0,p.initRules)(v,n,"decorators",y),E={problems:[],oasVersion:y,refTypes:new Map,visitorsData:{}};f&&S.push({severity:"error",ruleId:"remove-unused-components",visitor:b===c.SpecMajorVersion.OAS2?(0,m.RemoveUnusedComponents)({}):(0,g.RemoveUnusedComponents)({})});let O=yield(0,o.resolveDocument)({rootDocument:t,rootType:w.Root,externalRefResolver:i});k.length>0&&((0,l.walkDocument)({document:t,rootType:w.Root,normalizedVisitors:(0,s.normalizeVisitors)(k,w),resolvedRefMap:O,ctx:E}),O=yield(0,o.resolveDocument)({rootDocument:t,rootType:w.Root,externalRefResolver:i}));const _=(0,s.normalizeVisitors)([{severity:"error",ruleId:"bundler",visitor:x(b,u,d,t,O,h)},...S],w);return(0,l.walkDocument)({document:t,rootType:w.Root,normalizedVisitors:_,resolvedRefMap:O,ctx:E}),{bundle:t,problems:E.problems.map((e=>n.addProblemToIgnore(e))),fileDependencies:i.getFiles(),rootType:w.Root,refTypes:E.refTypes,visitorsData:E.visitorsData}}))}function v(e,t){switch(t){case c.SpecMajorVersion.OAS3:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";case"Response":return"responses";case"Example":return"examples";case"RequestBody":return"requestBodies";case"Header":return"headers";case"SecuritySchema":return"securitySchemes";case"Link":return"links";case"Callback":return"callbacks";default:return null}case c.SpecMajorVersion.OAS2:switch(e){case"Schema":return"definitions";case"Parameter":return"parameters";case"Response":return"responses";default:return null}case c.SpecMajorVersion.Async2:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";default:return null}}}function x(e,t,n,r,s,a){let l,p;const m={ref:{leave(i,l,c){if(!c.location||void 0===c.node)return void(0,d.reportUnresolvedRef)(c,l.report,l.location);if(c.location.source===r.source&&c.location.source===l.location.source&&"scalar"!==l.type.name&&!t)return;if(n&&(0,h.isRedoclyRegistryURL)(i.$ref))return;if(a&&(0,u.isAbsoluteUrl)(i.$ref))return;const p=v(l.type.name,e);p?t?(y(p,c,l),g(i,c,l)):(i.$ref=y(p,c,l),function(e,t,n){const i=(0,o.makeRefId)(n.location.source.absoluteRef,e.$ref);s.set(i,{document:r,isRemote:!1,node:t.node,nodePointer:e.$ref,resolved:!0})}(i,c,l)):g(i,c,l)}},Root:{enter(t,n){p=n.location,e===c.SpecMajorVersion.OAS3?l=t.components=t.components||{}:e===c.SpecMajorVersion.OAS2&&(l=t)}}};function g(e,t,n){if((0,f.isPlainObject)(t.node)){delete e.$ref;const n=Object.assign({},t.node,e);Object.assign(e,n)}else n.parent[n.key]=t.node}function y(t,n,r){l[t]=l[t]||{};const i=function(e,t,n){const[r,i]=[e.location.source.absoluteRef,e.location.pointer],o=l[t];let s="";const a=i.slice(2).split("/").filter(f.isTruthy);for(;a.length>0;)if(s=a.pop()+(s?`-${s}`:""),!o||!o[s]||b(o[s],e,n))return s;if(s=(0,u.refBaseName)(r)+(s?`_${s}`:""),!o[s]||b(o[s],e,n))return s;const c=s;let p=2;for(;o[s]&&!b(o[s],e,n);)s=`${c}-${p}`,p++;return o[s]||n.report({message:`Two schemas are referenced with the same name but different content. Renamed ${c} to ${s}.`,location:n.location,forceSeverity:"warn"}),s}(n,t,r);return l[t][i]=n.node,e===c.SpecMajorVersion.OAS3?`#/components/${t}/${i}`:`#/${t}/${i}`}function b(e,t,n){var r;return!(!(0,u.isRef)(e)||(null===(r=n.resolve(e,p.absolutePointer).location)||void 0===r?void 0:r.absolutePointer)!==t.location.absolutePointer)||i(e,t.node)}return e===c.SpecMajorVersion.OAS3&&(m.DiscriminatorMapping={leave(n,r){for(const i of Object.keys(n)){const o=n[i],s=r.resolve({$ref:o});if(!s.location||void 0===s.node)return void(0,d.reportUnresolvedRef)(s,r.report,r.location.child(i));const a=v("Schema",e);t?y(a,s,r):n[i]=y(a,s,r)}}}),m}!function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(y||(t.OasVersion=y={})),t.bundle=function(e){return r(this,void 0,void 0,(function*(){const{ref:t,doc:n,externalRefResolver:r=new o.BaseResolver(e.config.resolve),base:i=null}=e;if(!t&&!n)throw new Error("Document or reference is required.\n");const s=void 0===n?yield r.resolveDocument(i,t,!0):n;if(s instanceof Error)throw s;return b(Object.assign(Object.assign({document:s},e),{config:e.config.styleguide,externalRefResolver:r}))}))},t.bundleFromString=function(e){return r(this,void 0,void 0,(function*(){const{source:t,absoluteRef:n,externalRefResolver:r=new o.BaseResolver(e.config.resolve)}=e,i=(0,o.makeDocumentFromString)(t,n||"/");return b(Object.assign(Object.assign({document:i},e),{externalRefResolver:r,config:e.config.styleguide}))}))},t.bundleDocument=b,t.mapTypeToComponent=v},8921:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Config=t.StyleguideConfig=t.AVAILABLE_REGIONS=t.DOMAINS=t.DEFAULT_REGION=t.IGNORE_FILE=void 0;const r=n(7992),i=n(7975),o=n(970),s=n(8209),a=n(3101),l=n(1827),c=n(462),u=n(3873);t.IGNORE_FILE=".redocly.lint-ignore.yaml",t.DEFAULT_REGION="us",t.DOMAINS=function(){const e={us:"redocly.com",eu:"eu.redocly.com"},t=l.env.REDOCLY_DOMAIN;return(null==t?void 0:t.endsWith(".redocly.host"))&&(e[t.split(".")[0]]=t),"redoc.online"===t&&(e[t]=t),e}(),t.AVAILABLE_REGIONS=Object.keys(t.DOMAINS);class p{constructor(e,n){this.rawConfig=e,this.configFile=n,this.ignore={},this._usedRules=new Set,this._usedVersions=new Set,this.plugins=e.plugins||[],this.doNotResolveExamples=!!e.doNotResolveExamples,this.recommendedFallback=e.recommendedFallback||!1,this.rules={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.rules),e.oas2Rules),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.rules),e.oas3_0Rules),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.rules),e.oas3_1Rules),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.rules),e.async2Rules)},this.preprocessors={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.preprocessors),e.oas2Preprocessors),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.preprocessors),e.oas3_0Preprocessors),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.preprocessors),e.oas3_1Preprocessors),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.preprocessors),e.async2Preprocessors)},this.decorators={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.decorators),e.oas2Decorators),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.decorators),e.oas3_0Decorators),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.decorators),e.oas3_1Decorators),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.decorators),e.async2Decorators)},this.extendPaths=e.extendPaths||[],this.pluginPaths=e.pluginPaths||[],this.resolveIgnore(function(e){return e?(0,s.doesYamlFileExist)(e)?i.join(i.dirname(e),t.IGNORE_FILE):i.join(e,t.IGNORE_FILE):l.isBrowser?void 0:i.join(process.cwd(),t.IGNORE_FILE)}(n))}resolveIgnore(e){if(e&&(0,s.doesYamlFileExist)(e)){this.ignore=(0,o.parseYaml)(r.readFileSync(e,"utf-8"))||{};for(const t of Object.keys(this.ignore)){this.ignore[(0,u.isAbsoluteUrl)(t)?t:i.resolve(i.dirname(e),t)]=this.ignore[t];for(const e of Object.keys(this.ignore[t]))this.ignore[t][e]=new Set(this.ignore[t][e]);(0,u.isAbsoluteUrl)(t)||delete this.ignore[t]}}}saveIgnore(){const e=this.configFile?i.dirname(this.configFile):process.cwd(),n=i.join(e,t.IGNORE_FILE),a={};for(const t of Object.keys(this.ignore)){const n=a[(0,u.isAbsoluteUrl)(t)?t:(0,s.slash)(i.relative(e,t))]=this.ignore[t];for(const e of Object.keys(n))n[e]=Array.from(n[e])}r.writeFileSync(n,"# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n# See https://redoc.ly/docs/cli/ for more information.\n"+(0,o.stringifyYaml)(a))}addIgnore(e){const t=this.ignore,n=e.location[0];if(void 0===n.pointer)return;const r=t[n.source.absoluteRef]=t[n.source.absoluteRef]||{};(r[e.ruleId]=r[e.ruleId]||new Set).add(n.pointer)}addProblemToIgnore(e){const t=e.location[0];if(void 0===t.pointer)return e;const n=(this.ignore[t.source.absoluteRef]||{})[e.ruleId],r=n&&n.has(t.pointer);return r?Object.assign(Object.assign({},e),{ignored:r}):e}extendTypes(e,t){let n=e;for(const e of this.plugins)if(void 0!==e.typeExtension)switch(t){case a.SpecVersion.OAS3_0:case a.SpecVersion.OAS3_1:if(!e.typeExtension.oas3)continue;n=e.typeExtension.oas3(n,t);break;case a.SpecVersion.OAS2:if(!e.typeExtension.oas2)continue;n=e.typeExtension.oas2(n,t);break;case a.SpecVersion.Async2:if(!e.typeExtension.async2)continue;n=e.typeExtension.async2(n,t);break;default:throw new Error("Not implemented")}return n}getRuleSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.rules[t][e]||"off";return"string"==typeof n?{severity:n}:Object.assign({severity:"error"},n)}getPreprocessorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.preprocessors[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getDecoratorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.decorators[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getUnusedRules(){const e=[],t=[],n=[];for(const r of Array.from(this._usedVersions))e.push(...Object.keys(this.rules[r]).filter((e=>!this._usedRules.has(e)))),t.push(...Object.keys(this.decorators[r]).filter((e=>!this._usedRules.has(e)))),n.push(...Object.keys(this.preprocessors[r]).filter((e=>!this._usedRules.has(e))));return{rules:e,preprocessors:n,decorators:t}}getRulesForOasVersion(e){switch(e){case a.SpecMajorVersion.OAS3:const e=[];return this.plugins.forEach((t=>{var n;return(null===(n=t.preprocessors)||void 0===n?void 0:n.oas3)&&e.push(t.preprocessors.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.rules)||void 0===n?void 0:n.oas3)&&e.push(t.rules.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.decorators)||void 0===n?void 0:n.oas3)&&e.push(t.decorators.oas3)})),e;case a.SpecMajorVersion.OAS2:const t=[];return this.plugins.forEach((e=>{var n;return(null===(n=e.preprocessors)||void 0===n?void 0:n.oas2)&&t.push(e.preprocessors.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.rules)||void 0===n?void 0:n.oas2)&&t.push(e.rules.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.decorators)||void 0===n?void 0:n.oas2)&&t.push(e.decorators.oas2)})),t;case a.SpecMajorVersion.Async2:const n=[];return this.plugins.forEach((e=>{var t;return(null===(t=e.preprocessors)||void 0===t?void 0:t.async2)&&n.push(e.preprocessors.async2)})),this.plugins.forEach((e=>{var t;return(null===(t=e.rules)||void 0===t?void 0:t.async2)&&n.push(e.rules.async2)})),this.plugins.forEach((e=>{var t;return(null===(t=e.decorators)||void 0===t?void 0:t.async2)&&n.push(e.decorators.async2)})),n}}skipRules(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.rules[e][t]&&(this.rules[e][t]="off")}skipPreprocessors(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.preprocessors[e][t]&&(this.preprocessors[e][t]="off")}skipDecorators(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.decorators[e][t]&&(this.decorators[e][t]="off")}}t.StyleguideConfig=p,t.Config=class{constructor(e,t){this.rawConfig=e,this.configFile=t,this.apis=e.apis||{},this.styleguide=new p(e.styleguide||{},t),this.theme=e.theme||{},this.resolve=(0,c.getResolveConfig)(null==e?void 0:e.resolve),this.region=e.region,this.organization=e.organization,this.files=e.files||[],this.telemetry=e.telemetry}}},2900:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.initRules=void 0;const r=n(8209);t.initRules=function(e,t,n,i){return e.flatMap((e=>Object.keys(e).map((r=>{const o=e[r],s="rules"===n?t.getRuleSettings(r,i):"preprocessors"===n?t.getPreprocessorSettings(r,i):t.getDecoratorSettings(r,i);if("off"===s.severity)return;const a=s.severity,l=o(s);return Array.isArray(l)?l.map((e=>({severity:a,ruleId:r,visitor:e}))):{severity:a,ruleId:r,visitor:l}})))).flatMap((e=>e)).filter(r.isDefined)}},462:function(e,t,n){"use strict";var r=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);it[e]));n[e]&&null===t&&(0,i.showWarningForDeprecatedField)(e),n[e]&&t&&n[t]&&(0,i.showErrorForDeprecatedField)(e,t),n[e]&&r&&n[r]&&(0,i.showErrorForDeprecatedField)(e,t,r),(n[e]||o)&&(0,i.showWarningForDeprecatedField)(e,t,r)}t.parsePresetName=function(e){if(e.indexOf("/")>-1){const[t,n]=e.split("/");return{pluginId:t,configName:n}}return{pluginId:"",configName:e}},t.transformApiDefinitionsToApis=a,t.prefixRules=function(e,t){if(!t)return e;const n={};for(const r of Object.keys(e))n[`${t}/${r}`]=e[r];return n},t.mergeExtends=function(e){const t={rules:{},oas2Rules:{},oas3_0Rules:{},oas3_1Rules:{},async2Rules:{},preprocessors:{},oas2Preprocessors:{},oas3_0Preprocessors:{},oas3_1Preprocessors:{},async2Preprocessors:{},decorators:{},oas2Decorators:{},oas3_0Decorators:{},oas3_1Decorators:{},async2Decorators:{},plugins:[],pluginPaths:[],extendPaths:[]};for(const n of e){if(n.extends)throw new Error(`'extends' is not supported in shared configs yet: ${JSON.stringify(n,null,2)}.`);Object.assign(t.rules,n.rules),Object.assign(t.oas2Rules,n.oas2Rules),(0,i.assignExisting)(t.oas2Rules,n.rules||{}),Object.assign(t.oas3_0Rules,n.oas3_0Rules),(0,i.assignExisting)(t.oas3_0Rules,n.rules||{}),Object.assign(t.oas3_1Rules,n.oas3_1Rules),(0,i.assignExisting)(t.oas3_1Rules,n.rules||{}),Object.assign(t.async2Rules,n.async2Rules),(0,i.assignExisting)(t.async2Rules,n.rules||{}),Object.assign(t.preprocessors,n.preprocessors),Object.assign(t.oas2Preprocessors,n.oas2Preprocessors),(0,i.assignExisting)(t.oas2Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_0Preprocessors,n.oas3_0Preprocessors),(0,i.assignExisting)(t.oas3_0Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_1Preprocessors,n.oas3_1Preprocessors),(0,i.assignExisting)(t.oas3_1Preprocessors,n.preprocessors||{}),Object.assign(t.async2Preprocessors,n.async2Preprocessors),(0,i.assignExisting)(t.async2Preprocessors,n.preprocessors||{}),Object.assign(t.decorators,n.decorators),Object.assign(t.oas2Decorators,n.oas2Decorators),(0,i.assignExisting)(t.oas2Decorators,n.decorators||{}),Object.assign(t.oas3_0Decorators,n.oas3_0Decorators),(0,i.assignExisting)(t.oas3_0Decorators,n.decorators||{}),Object.assign(t.oas3_1Decorators,n.oas3_1Decorators),(0,i.assignExisting)(t.oas3_1Decorators,n.decorators||{}),Object.assign(t.async2Decorators,n.async2Decorators),(0,i.assignExisting)(t.async2Decorators,n.decorators||{}),t.plugins.push(...n.plugins||[]),t.pluginPaths.push(...n.pluginPaths||[]),t.extendPaths.push(...new Set(n.extendPaths))}return t},t.getMergedConfig=function(e,t){var n,r,s,a,l,c,u,p;const d=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.styleguide)||void 0===t?void 0:t.extendPaths})),null===(r=null===(n=e.rawConfig)||void 0===n?void 0:n.styleguide)||void 0===r?void 0:r.extendPaths].flat().filter(i.isTruthy),f=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.styleguide)||void 0===t?void 0:t.pluginPaths})),null===(a=null===(s=e.rawConfig)||void 0===s?void 0:s.styleguide)||void 0===a?void 0:a.pluginPaths].flat().filter(i.isTruthy);return t?new o.Config(Object.assign(Object.assign({},e.rawConfig),{styleguide:Object.assign(Object.assign({},e.apis[t]?e.apis[t].styleguide:e.rawConfig.styleguide),{extendPaths:d,pluginPaths:f}),theme:Object.assign(Object.assign({},e.rawConfig.theme),null===(l=e.apis[t])||void 0===l?void 0:l.theme),files:[...e.files,...null!==(p=null===(u=null===(c=e.apis)||void 0===c?void 0:c[t])||void 0===u?void 0:u.files)&&void 0!==p?p:[]]}),e.configFile):e},t.checkForDeprecatedFields=u,t.transformConfig=function(e){var t,n;const i=[["apiDefinitions","apis",void 0],["referenceDocs","openapi","theme"],["lint",void 0,void 0],["styleguide",void 0,void 0],["features.openapi","openapi","theme"]];for(const[t,n,r]of i)u(t,n,e,r);const{apis:o,apiDefinitions:p,referenceDocs:d,lint:f}=e,h=r(e,["apis","apiDefinitions","referenceDocs","lint"]),{styleguideConfig:m,rawConfigRest:g}=l(h),y=Object.assign({theme:{openapi:Object.assign(Object.assign(Object.assign({},d),e["features.openapi"]),null===(t=e.theme)||void 0===t?void 0:t.openapi),mockServer:Object.assign(Object.assign({},e["features.mockServer"]),null===(n=e.theme)||void 0===n?void 0:n.mockServer)},apis:c(o)||a(p),styleguide:m||f},g);return function(e){var t,n;let r=Object.assign({},null===(t=e.styleguide)||void 0===t?void 0:t.rules);for(const t of Object.values(e.apis||{}))r=Object.assign(Object.assign({},r),null===(n=null==t?void 0:t.styleguide)||void 0===n?void 0:n.rules);for(const e of Object.keys(r))e.startsWith("assert/")&&s.logger.warn(`\nThe 'assert/' syntax in ${e} is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/\n`)}(y),y},t.getResolveConfig=function(e){var t,n;return{http:{headers:null!==(n=null===(t=null==e?void 0:e.http)||void 0===t?void 0:t.headers)&&void 0!==n?n:[],customFetch:void 0}}},t.getUniquePlugins=function(e){const t=new Set,n=[];for(const r of e)t.has(r.id)?r.id&&s.logger.warn(`Duplicate plugin id "${s.colorize.red(r.id)}".\n`):(n.push(r),t.add(r.id));return n};class p extends Error{}t.ConfigValidationError=p},1827:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.env=t.isBrowser=void 0,t.isBrowser="undefined"!=typeof window||"undefined"!=typeof self||"undefined"==typeof process,t.env=t.isBrowser?{}:{}||{}},970:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.stringifyYaml=t.parseYaml=void 0;const r=n(7210),i=r.JSON_SCHEMA.extend({implicit:[r.types.merge],explicit:[r.types.binary,r.types.omap,r.types.pairs,r.types.set]});t.parseYaml=(e,t)=>(0,r.load)(e,Object.assign({schema:i},t)),t.stringifyYaml=(e,t)=>(0,r.dump)(e,t)},2678:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.logger=t.colorize=t.colorOptions=void 0;const r=n(8825);var i=n(8825);Object.defineProperty(t,"colorOptions",{enumerable:!0,get:function(){return i.options}});const o=n(1827),s=n(8209);t.colorize=new Proxy(r,{get(e,t){return o.isBrowser?s.identity:e[t]}}),t.logger=new class{stderr(e){return process.stderr.write(e)}info(e){return o.isBrowser?console.log(e):this.stderr(e)}warn(e){return o.isBrowser?console.warn(e):this.stderr(t.colorize.yellow(e))}error(e){return o.isBrowser?console.error(e):this.stderr(t.colorize.red(e))}}},3101:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getTypes=t.getMajorSpecVersion=t.detectSpec=t.SpecMajorVersion=t.SpecVersion=void 0;const r=n(4409),i=n(4154),o=n(2082),s=n(264);var a,l;!function(e){e.OAS2="oas2",e.OAS3_0="oas3_0",e.OAS3_1="oas3_1",e.Async2="async2"}(a||(t.SpecVersion=a={})),function(e){e.OAS2="oas2",e.OAS3="oas3",e.Async2="async2"}(l||(t.SpecMajorVersion=l={}));const c={[a.OAS2]:r.Oas2Types,[a.OAS3_0]:i.Oas3Types,[a.OAS3_1]:o.Oas3_1Types,[a.Async2]:s.AsyncApi2Types};t.detectSpec=function(e){if("object"!=typeof e)throw new Error("Document must be JSON object, got "+typeof e);if(e.openapi&&"string"!=typeof e.openapi)throw new Error(`Invalid OpenAPI version: should be a string but got "${typeof e.openapi}"`);if(e.openapi&&e.openapi.startsWith("3.0"))return a.OAS3_0;if(e.openapi&&e.openapi.startsWith("3.1"))return a.OAS3_1;if(e.swagger&&"2.0"===e.swagger)return a.OAS2;if(e.openapi||e.swagger)throw new Error(`Unsupported OpenAPI version: ${e.openapi||e.swagger}`);if(e.asyncapi&&e.asyncapi.startsWith("2."))return a.Async2;if(e.asyncapi)throw new Error(`Unsupported AsyncAPI version: ${e.asyncapi}`);throw new Error("Unsupported specification")},t.getMajorSpecVersion=function(e){return e===a.OAS2?l.OAS2:e===a.Async2?l.Async2:l.OAS3},t.getTypes=function(e){return c[e]}},4125:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isRedoclyRegistryURL=t.RedoclyClient=void 0;const i=n(3986),o=n(7975),s=n(2941),a=n(919),l=n(8921),c=n(1827),u=n(8209),p=n(2678),d=".redocly-config.json";t.RedoclyClient=class{constructor(e){this.accessTokens={},this.region=this.loadRegion(e),this.loadTokens(),this.domain=e?l.DOMAINS[e]:c.env.REDOCLY_DOMAIN||l.DOMAINS[l.DEFAULT_REGION],c.env.REDOCLY_DOMAIN=this.domain,this.registryApi=new a.RegistryApi(this.accessTokens,this.region)}loadRegion(e){if(e&&!l.DOMAINS[e])throw new Error(`Invalid argument: region in config file.\nGiven: ${p.colorize.green(e)}, choices: "us", "eu".`);return c.env.REDOCLY_DOMAIN?l.AVAILABLE_REGIONS.find((e=>l.DOMAINS[e]===c.env.REDOCLY_DOMAIN))||l.DEFAULT_REGION:e||l.DEFAULT_REGION}getRegion(){return this.region}hasTokens(){return(0,u.isNotEmptyObject)(this.accessTokens)}hasToken(){return!!this.accessTokens[this.region]}getAuthorizationHeader(){return r(this,void 0,void 0,(function*(){return this.accessTokens[this.region]}))}setAccessTokens(e){this.accessTokens=e}loadTokens(){const e=(0,o.resolve)((0,s.homedir)(),d),t=this.readCredentialsFile(e);(0,u.isNotEmptyObject)(t)&&this.setAccessTokens(Object.assign(Object.assign({},t),t.token&&!t[this.region]&&{[this.region]:t.token})),c.env.REDOCLY_AUTHORIZATION&&this.setAccessTokens(Object.assign(Object.assign({},this.accessTokens),{[this.region]:c.env.REDOCLY_AUTHORIZATION}))}getAllTokens(){return Object.entries(this.accessTokens).filter((([e])=>l.AVAILABLE_REGIONS.includes(e))).map((([e,t])=>({region:e,token:t})))}getValidTokens(){return r(this,void 0,void 0,(function*(){const e=this.getAllTokens(),t=yield Promise.allSettled(e.map((({token:e,region:t})=>this.verifyToken(e,t))));return e.filter(((e,n)=>"fulfilled"===t[n].status)).map((({token:e,region:t})=>({token:e,region:t,valid:!0})))}))}getTokens(){return r(this,void 0,void 0,(function*(){return this.hasTokens()?yield this.getValidTokens():[]}))}isAuthorizedWithRedoclyByRegion(){return r(this,void 0,void 0,(function*(){if(!this.hasTokens())return!1;const e=this.accessTokens[this.region];if(!e)return!1;try{return yield this.verifyToken(e,this.region),!0}catch(e){return!1}}))}isAuthorizedWithRedocly(){return r(this,void 0,void 0,(function*(){return this.hasTokens()&&(0,u.isNotEmptyObject)(yield this.getValidTokens())}))}readCredentialsFile(e){return(0,i.existsSync)(e)?JSON.parse((0,i.readFileSync)(e,"utf-8")):{}}verifyToken(e,t,n=!1){return r(this,void 0,void 0,(function*(){return this.registryApi.authStatus(e,t,n)}))}login(e,t=!1){return r(this,void 0,void 0,(function*(){const n=(0,o.resolve)((0,s.homedir)(),d);try{yield this.verifyToken(e,this.region,t)}catch(e){throw new Error("Authorization failed. Please check if you entered a valid API key.")}const r=Object.assign(Object.assign({},this.readCredentialsFile(n)),{[this.region]:e,token:e});this.accessTokens=r,this.registryApi.setAccessTokens(r),(0,i.writeFileSync)(n,JSON.stringify(r,null,2))}))}logout(){const e=(0,o.resolve)((0,s.homedir)(),d);(0,i.existsSync)(e)&&(0,i.unlinkSync)(e)}},t.isRedoclyRegistryURL=function(e){const t=c.env.REDOCLY_DOMAIN||l.DOMAINS[l.DEFAULT_REGION],n="redocly.com"===t?"redoc.ly":t;return!(!e.startsWith(`https://api.${t}/registry/`)&&!e.startsWith(`https://api.${n}/registry/`))}},919:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryApi=void 0;const i=n(8381),o=n(8921),s=n(8209),a=n(2079).rE;t.RegistryApi=class{constructor(e,t){this.accessTokens=e,this.region=t}get accessToken(){return(0,s.isNotEmptyObject)(this.accessTokens)&&this.accessTokens[this.region]}getBaseUrl(e=o.DEFAULT_REGION){return`https://api.${o.DOMAINS[e]}/registry`}setAccessTokens(e){return this.accessTokens=e,this}request(e="",t={},n){var o,s;return r(this,void 0,void 0,(function*(){const r="undefined"!=typeof process&&(null===(o={})||void 0===o?void 0:o.REDOCLY_CLI_COMMAND)||"",l="undefined"!=typeof process&&(null===(s={})||void 0===s?void 0:s.REDOCLY_ENVIRONMENT)||"",c=Object.assign({},t.headers||{},{"x-redocly-cli-version":a,"user-agent":`redocly-cli / ${a} ${r} ${l}`});if(!c.hasOwnProperty("authorization"))throw new Error("Unauthorized");const u=yield(0,i.default)(`${this.getBaseUrl(n)}${e}`,Object.assign({},t,{headers:c}));if(401===u.status)throw new Error("Unauthorized");if(404===u.status){const e=yield u.json();throw new Error(e.code)}return u}))}authStatus(e,t,n=!1){return r(this,void 0,void 0,(function*(){try{const n=yield this.request("",{headers:{authorization:e}},t);return yield n.json()}catch(e){throw n&&console.log(e),e}}))}prepareFileUpload({organizationId:e,name:t,version:n,filesHash:i,filename:o,isUpsert:s}){return r(this,void 0,void 0,(function*(){const r=yield this.request(`/${e}/${t}/${n}/prepare-file-upload`,{method:"POST",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({filesHash:i,filename:o,isUpsert:s})},this.region);if(r.ok)return r.json();throw new Error("Could not prepare file upload")}))}pushApi({organizationId:e,name:t,version:n,rootFilePath:i,filePaths:o,branch:s,isUpsert:a,isPublic:l,batchId:c,batchSize:u}){return r(this,void 0,void 0,(function*(){if(!(yield this.request(`/${e}/${t}/${n}`,{method:"PUT",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({rootFilePath:i,filePaths:o,branch:s,isUpsert:a,isPublic:l,batchId:c,batchSize:u})},this.region)).ok)throw new Error("Could not push api")}))}}},3873:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isAnchor=t.isMappingRef=t.isAbsoluteUrl=t.refBaseName=t.pointerBaseName=t.parsePointer=t.parseRef=t.escapePointer=t.unescapePointer=t.Location=t.isRef=t.joinPointer=void 0;const r=n(8209);function i(e,t){return""===e&&(e="#/"),"/"===e[e.length-1]?e+t:e+"/"+t}t.joinPointer=i,t.isRef=function(e){return e&&"string"==typeof e.$ref};class o{constructor(e,t){this.source=e,this.pointer=t}child(e){return new o(this.source,i(this.pointer,(Array.isArray(e)?e:[e]).map(a).join("/")))}key(){return Object.assign(Object.assign({},this),{reportOnKey:!0})}get absolutePointer(){return this.source.absoluteRef+("#/"===this.pointer?"":this.pointer)}}function s(e){return decodeURIComponent(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function a(e){return"number"==typeof e?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}t.Location=o,t.unescapePointer=s,t.escapePointer=a,t.parseRef=function(e){const[t,n]=e.split("#/");return{uri:t||null,pointer:n?n.split("/").map(s).filter(r.isTruthy):[]}},t.parsePointer=function(e){return e.substr(2).split("/").map(s)},t.pointerBaseName=function(e){const t=e.split("/");return t[t.length-1]},t.refBaseName=function(e){const t=e.split(/[\/\\]/);return t[t.length-1].replace(/\.[^.]+$/,"")},t.isAbsoluteUrl=function(e){return e.startsWith("http://")||e.startsWith("https://")},t.isMappingRef=function(e){return e.startsWith("#")||e.startsWith("https://")||e.startsWith("http://")||e.startsWith("./")||e.startsWith("../")||e.indexOf("/")>-1},t.isAnchor=function(e){return/^#[A-Za-z][A-Za-z0-9\-_:.]*$/.test(e)}},2928:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.resolveDocument=t.BaseResolver=t.makeDocumentFromString=t.makeRefId=t.YamlParseError=t.ResolveError=t.Source=void 0;const i=n(7411),o=n(7975),s=n(3873),a=n(1990),l=n(8209);class c{constructor(e,t,n){this.absoluteRef=e,this.body=t,this.mimeType=n}getAst(e){var t;return void 0===this._ast&&(this._ast=null!==(t=e(this.body,{filename:this.absoluteRef}))&&void 0!==t?t:void 0,this._ast&&0===this._ast.kind&&""===this._ast.value&&1!==this._ast.startPosition&&(this._ast.startPosition=1,this._ast.endPosition=1)),this._ast}getLines(){return void 0===this._lines&&(this._lines=this.body.split(/\r\n|[\n\r]/g)),this._lines}}t.Source=c;class u extends Error{constructor(e){super(e.message),this.originalError=e,Object.setPrototypeOf(this,u.prototype)}}t.ResolveError=u;const p=/\((\d+):(\d+)\)$/;class d extends Error{constructor(e,t){super(e.message.split("\n")[0]),this.originalError=e,this.source=t,Object.setPrototypeOf(this,d.prototype);const[,n,r]=this.message.match(p)||[];this.line=parseInt(n,10),this.col=parseInt(r,10)}}function f(e,t){return e+"::"+t}function h(e,t){return{prev:e,node:t}}t.YamlParseError=d,t.makeRefId=f,t.makeDocumentFromString=function(e,t){const n=new c(t,e);try{return{source:n,parsed:(0,l.parseYaml)(e,{filename:t})}}catch(e){throw new d(e,n)}},t.BaseResolver=class{constructor(e={http:{headers:[]}}){this.config=e,this.cache=new Map}getFiles(){return new Set(Array.from(this.cache.keys()))}resolveExternalRef(e,t){return(0,s.isAbsoluteUrl)(t)?t:e&&(0,s.isAbsoluteUrl)(e)?new URL(t,e).href:o.resolve(e?o.dirname(e):process.cwd(),t)}loadExternalRef(e){return r(this,void 0,void 0,(function*(){try{if((0,s.isAbsoluteUrl)(e)){const{body:t,mimeType:n}=yield(0,l.readFileFromUrl)(e,this.config.http);return new c(e,t,n)}{if(i.lstatSync(e).isDirectory())throw new Error(`Expected a file but received a folder at ${e}`);const t=yield i.promises.readFile(e,"utf-8");return new c(e,t.replace(/\r\n/g,"\n"))}}catch(e){throw e.message=e.message.replace(", lstat",""),new u(e)}}))}parseDocument(e,t=!1){var n;const r=e.absoluteRef.substr(e.absoluteRef.lastIndexOf("."));if(![".json",".json",".yml",".yaml"].includes(r)&&!(null===(n=e.mimeType)||void 0===n?void 0:n.match(/(json|yaml|openapi)/))&&!t)return{source:e,parsed:e.body};try{return{source:e,parsed:(0,l.parseYaml)(e.body,{filename:e.absoluteRef})}}catch(t){throw new d(t,e)}}resolveDocument(e,t,n=!1){return r(this,void 0,void 0,(function*(){const r=this.resolveExternalRef(e,t),i=this.cache.get(r);if(i)return i;const o=this.loadExternalRef(r).then((e=>this.parseDocument(e,n)));return this.cache.set(r,o),o}))}};const m={name:"unknown",properties:{}},g={name:"scalar",properties:{}};t.resolveDocument=function(e){return r(this,void 0,void 0,(function*(){const{rootDocument:t,externalRefResolver:n,rootType:i}=e,o=new Map,c=new Set,u=[];let p;!function e(t,i,p,d){const y=i.source.absoluteRef,b=new Map;function v(e,t,i){return r(this,void 0,void 0,(function*(){if(function(e,t){for(;e;){if(e.node===t)return!0;e=e.prev}return!1}(i.prev,t))throw new Error("Self-referencing circular pointer");if((0,s.isAnchor)(t.$ref)){yield(0,l.nextTick)();const n={resolved:!0,isRemote:!1,node:b.get(t.$ref),document:e,nodePointer:t.$ref},r=f(e.source.absoluteRef,t.$ref);return o.set(r,n),n}const{uri:r,pointer:a}=(0,s.parseRef)(t.$ref),c=null!==r;let u;try{u=c?yield n.resolveDocument(e.source.absoluteRef,r):e}catch(n){const r={resolved:!1,isRemote:c,document:void 0,error:n},i=f(e.source.absoluteRef,t.$ref);return o.set(i,r),r}let p={resolved:!0,document:u,isRemote:c,node:e.parsed,nodePointer:"#/"},d=u.parsed;const m=a;for(const e of m){if("object"!=typeof d){d=void 0;break}if(void 0!==d[e])d=d[e],p.nodePointer=(0,s.joinPointer)(p.nodePointer,(0,s.escapePointer)(e));else{if(!(0,s.isRef)(d)){d=void 0;break}if(p=yield v(u,d,h(i,d)),u=p.document||u,"object"!=typeof p.node){d=void 0;break}d=p.node[e],p.nodePointer=(0,s.joinPointer)(p.nodePointer,(0,s.escapePointer)(e))}}p.node=d,p.document=u;const g=f(e.source.absoluteRef,t.$ref);return p.document&&(0,s.isRef)(d)&&(p=yield v(p.document,d,h(i,d))),o.set(g,p),Object.assign({},p)}))}!function t(n,r,o){if("object"!=typeof n||null===n)return;const l=`${r.name}::${o}`;if(c.has(l))return;c.add(l);const[p,d]=Object.entries(n).find((([e])=>"$anchor"===e))||[];if(d&&b.set(`#${d}`,n),Array.isArray(n)){const e=r.items;if(void 0===e&&r!==m&&r!==a.SpecExtension)return;for(let r=0;r{t.resolved&&e(t.node,t.document,t.nodePointer,r)}));u.push(t)}}}(t,d,y+p)}(t.parsed,t,"#/",i);do{p=yield Promise.all(u)}while(u.length!==p.length);return o}))}},3416:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportUnresolvedRef=t.NoUnresolvedRefs=void 0;const r=n(2928);function i(e,t,n){var i;const o=e.error;o instanceof r.YamlParseError&&t({message:"Failed to parse: "+o.message,location:{source:o.source,pointer:void 0,start:{col:o.col,line:o.line}}});const s=null===(i=e.error)||void 0===i?void 0:i.message;t({location:n,message:"Can't resolve $ref"+(s?": "+s:"")})}t.NoUnresolvedRefs=()=>({ref:{leave(e,{report:t,location:n},r){void 0===r.node&&i(r,t,n)}},DiscriminatorMapping(e,{report:t,resolve:n,location:r}){for(const o of Object.keys(e)){const s=n({$ref:e[o]});if(void 0!==s.node)return;i(s,t,r.child(o))}}}),t.reportUnresolvedRef=i},474:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(8209);t.RemoveUnusedComponents=()=>{const e=new Map;function t(t,n,r){var i;e.set(t.absolutePointer,{used:(null===(i=e.get(t.absolutePointer))||void 0===i?void 0:i.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:i}){if(["Schema","Parameter","Response","SecurityScheme"].includes(n.name)){const n=r(t);if(!n.location)return;const[o,s]=n.location.absolutePointer.split("#",2),a=`${o}#${s.split("/").slice(0,3).join("/")}`;e.set(a,{used:!0,name:i.toString()})}}},Root:{leave(t,n){const i=n.getVisitorData();i.removedCount=0;const o=new Set;e.forEach((e=>{const{used:n,name:r,componentType:s}=e;!n&&s&&(o.add(s),delete t[s][r],i.removedCount++)}));for(const e of o)(0,r.isEmptyObject)(t[e])&&delete t[e]}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"definitions",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedSecuritySchemes:{SecurityScheme(e,{location:n,key:r}){t(n,"securityDefinitions",r.toString())}}}}},4335:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(8209);t.RemoveUnusedComponents=()=>{const e=new Map;function t(t,n,r){var i;e.set(t.absolutePointer,{used:(null===(i=e.get(t.absolutePointer))||void 0===i?void 0:i.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:i}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;const[o,s]=n.location.absolutePointer.split("#",2),a=`${o}#${s.split("/").slice(0,4).join("/")}`;e.set(a,{used:!0,name:i.toString()})}}},Root:{leave(t,n){const i=n.getVisitorData();i.removedCount=0,e.forEach((e=>{const{used:n,componentType:o,name:s}=e;if(!n&&o&&t.components){const e=t.components[o];delete e[s],i.removedCount++,(0,r.isEmptyObject)(e)&&delete t.components[o]}})),(0,r.isEmptyObject)(t.components)&&delete t.components}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"schemas",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,"examples",r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,"requestBodies",r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,"headers",r.toString())}}}}},264:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AsyncApi2Types=void 0;const r=n(1990),i=n(3873),o={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},s={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},a={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},l={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},c={properties:{$id:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",myArbitraryKeyword:{type:"boolean"},title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:(0,r.listOf)("Schema"),prefixItems:(0,r.listOf)("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},dependencies:{type:"object"}}},u={properties:{},additionalProperties:e=>(0,i.isMappingRef)(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},p={properties:{type:{enum:["userPassword","apiKey","X509","symmetricEncryption","asymmetricEncryption","httpApiKey","http","oauth2","openIdConnect","plain","scramSha256","scramSha512","gssapi"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie","user","password"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","in"];case"httpApiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","in","description"];case"httpApiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"},d={properties:{}};o.properties.http=d;const f={properties:{}};s.properties.http=f;const h={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.http=h;const m={properties:{type:{type:"string"},method:{type:"string",enum:["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS","CONNECT","TRACE"]},headers:"Schema",bindingVersion:{type:"string"}}};l.properties.http=m;const g={properties:{method:{type:"string"},query:"Schema",headers:"Schema",bindingVersion:{type:"string"}}};o.properties.ws=g;const y={properties:{}};s.properties.ws=y;const b={properties:{}};a.properties.ws=b;const v={properties:{}};l.properties.ws=v;const x={properties:{topic:{type:"string"},partitions:{type:"integer"},replicas:{type:"integer"},topicConfiguration:"KafkaTopicConfiguration",bindingVersion:{type:"string"}}};o.properties.kafka=x;const w={properties:{}};s.properties.kafka=w;const k={properties:{key:"Schema",schemaIdLocation:{type:"string"},schemaIdPayloadEncoding:{type:"string"},schemaLookupStrategy:{type:"string"},bindingVersion:{type:"string"}}};a.properties.kafka=k;const S={properties:{groupId:"Schema",clientId:"Schema",bindingVersion:{type:"string"}}};l.properties.kafka=S;const E={properties:{destination:{type:"string"},destinationType:{type:"string"},bindingVersion:{type:"string"}}};o.properties.anypointmq=E;const O={properties:{}};s.properties.anypointmq=O;const _={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.anypointmq=_;const A={properties:{}};l.properties.anypointmq=A;const C={properties:{}};o.properties.amqp=C;const j={properties:{}};s.properties.amqp=j;const P={properties:{contentEncoding:{type:"string"},messageType:{type:"string"},bindingVersion:{type:"string"}}};a.properties.amqp=P;const T={properties:{expiration:{type:"integer"},userId:{type:"string"},cc:{type:"array",items:{type:"string"}},priority:{type:"integer"},deliveryMode:{type:"integer"},mandatory:{type:"boolean"},bcc:{type:"array",items:{type:"string"}},replyTo:{type:"string"},timestamp:{type:"boolean"},ack:{type:"boolean"},bindingVersion:{type:"string"}}};l.properties.amqp=T;const I={properties:{}};o.properties.amqp1=I;const R={properties:{}};s.properties.amqp1=R;const N={properties:{}};a.properties.amqp1=N;const $={properties:{}};l.properties.amqp1=$;const L={properties:{qos:{type:"integer"},retain:{type:"boolean"},bindingVersion:{type:"string"}}};o.properties.mqtt=L;const D={properties:{clientId:{type:"string"},cleanSession:{type:"boolean"},lastWill:"MqttServerBindingLastWill",keepAlive:{type:"integer"},bindingVersion:{type:"string"}}};s.properties.mqtt=D;const M={properties:{bindingVersion:{type:"string"}}};a.properties.mqtt=M;const F={properties:{qos:{type:"integer"},retain:{type:"boolean"},bindingVersion:{type:"string"}}};l.properties.mqtt=F;const z={properties:{}};o.properties.mqtt5=z;const B={properties:{}};s.properties.mqtt5=B;const U={properties:{}};a.properties.mqtt5=U;const q={properties:{}};l.properties.mqtt5=q;const V={properties:{}};o.properties.nats=V;const W={properties:{}};s.properties.nats=W;const H={properties:{}};a.properties.nats=H;const Y={properties:{queue:{type:"string"},bindingVersion:{type:"string"}}};l.properties.nats=Y;const G={properties:{destination:{type:"string"},destinationType:{type:"string"},bindingVersion:{type:"string"}}};o.properties.jms=G;const Q={properties:{}};s.properties.jms=Q;const X={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.jms=X;const K={properties:{headers:"Schema",bindingVersion:{type:"string"}}};l.properties.jms=K;const Z={properties:{}};o.properties.solace=Z;const J={properties:{bindingVersion:{type:"string"},msgVpn:{type:"string"}}};s.properties.solace=J;const ee={properties:{}};a.properties.solace=ee;const te={properties:{bindingVersion:{type:"string"},destinations:(0,r.listOf)("SolaceDestination")}};l.properties.solace=te;const ne={properties:{}};o.properties.stomp=ne;const re={properties:{}};s.properties.stomp=re;const ie={properties:{}};a.properties.stomp=ie;const oe={properties:{}};l.properties.stomp=oe;const se={properties:{}};o.properties.redis=se;const ae={properties:{}};s.properties.redis=ae;const le={properties:{}};a.properties.redis=le;const ce={properties:{}};l.properties.redis=ce;const ue={properties:{}};o.properties.mercure=ue;const pe={properties:{}};s.properties.mercure=pe;const de={properties:{}};a.properties.mercure=de;const fe={properties:{}};l.properties.mercure=fe,t.AsyncApi2Types={Root:{properties:{asyncapi:null,info:"Info",id:{type:"string"},servers:"ServerMap",channels:"ChannelMap",components:"Components",tags:"TagList",externalDocs:"ExternalDocs",defaultContentType:{type:"string"}},required:["asyncapi","channels","info"]},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},TagList:(0,r.listOf)("Tag"),ServerMap:{properties:{},additionalProperties:(e,t)=>t.match(/^[A-Za-z0-9_\-]+$/)?"Server":void 0},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},Server:{properties:{url:{type:"string"},protocol:{type:"string"},protocolVersion:{type:"string"},description:{type:"string"},variables:"ServerVariablesMap",security:"SecurityRequirementList",bindings:"ServerBindings",tags:"TagList"},required:["url","protocol"]},ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:{type:"string"},examples:{type:"array",items:{type:"string"}}},required:[]},ServerVariablesMap:(0,r.mapOf)("ServerVariable"),SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},HttpServerBinding:f,HttpChannelBinding:d,HttpMessageBinding:h,HttpOperationBinding:m,WsServerBinding:y,WsChannelBinding:g,WsMessageBinding:b,WsOperationBinding:v,KafkaServerBinding:w,KafkaTopicConfiguration:{properties:{"cleanup.policy":{type:"array",items:{enum:["delete","compact"]}},"retention.ms":{type:"integer"},"retention.bytes":{type:"integer"},"delete.retention.ms":{type:"integer"},"max.message.bytes":{type:"integer"}}},KafkaChannelBinding:x,KafkaMessageBinding:k,KafkaOperationBinding:S,AnypointmqServerBinding:O,AnypointmqChannelBinding:E,AnypointmqMessageBinding:_,AnypointmqOperationBinding:A,AmqpServerBinding:j,AmqpChannelBinding:C,AmqpMessageBinding:P,AmqpOperationBinding:T,Amqp1ServerBinding:R,Amqp1ChannelBinding:I,Amqp1MessageBinding:N,Amqp1OperationBinding:$,MqttServerBindingLastWill:{properties:{topic:{type:"string"},qos:{type:"integer"},message:{type:"string"},retain:{type:"boolean"}}},MqttServerBinding:D,MqttChannelBinding:L,MqttMessageBinding:M,MqttOperationBinding:F,Mqtt5ServerBinding:B,Mqtt5ChannelBinding:z,Mqtt5MessageBinding:U,Mqtt5OperationBinding:q,NatsServerBinding:W,NatsChannelBinding:V,NatsMessageBinding:H,NatsOperationBinding:Y,JmsServerBinding:Q,JmsChannelBinding:G,JmsMessageBinding:X,JmsOperationBinding:K,SolaceServerBinding:J,SolaceChannelBinding:Z,SolaceMessageBinding:ee,SolaceDestination:{properties:{destinationType:{type:"string",enum:["queue","topic"]},deliveryMode:{type:"string",enum:["direct","persistent"]},"queue.name":{type:"string"},"queue.topicSubscriptions":{type:"array",items:{type:"string"}},"queue.accessType":{type:"string",enum:["exclusive","nonexclusive"]},"queue.maxMsgSpoolSize":{type:"string"},"queue.maxTtl":{type:"string"},"topic.topicSubscriptions":{type:"array",items:{type:"string"}}}},SolaceOperationBinding:te,StompServerBinding:re,StompChannelBinding:ne,StompMessageBinding:ie,StompOperationBinding:oe,RedisServerBinding:ae,RedisChannelBinding:se,RedisMessageBinding:le,RedisOperationBinding:ce,MercureServerBinding:pe,MercureChannelBinding:ue,MercureMessageBinding:de,MercureOperationBinding:fe,ServerBindings:s,ChannelBindings:o,ChannelMap:{properties:{},additionalProperties:"Channel"},Channel:{properties:{description:{type:"string"},subscribe:"Operation",publish:"Operation",parameters:"ParametersMap",bindings:"ChannelBindings",servers:{type:"array",items:{type:"string"}}}},Parameter:{properties:{description:{type:"string"},schema:"Schema",location:{type:"string"}}},ParametersMap:(0,r.mapOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},security:"SecurityRequirementList",bindings:"OperationBindings",traits:"OperationTraitList",message:"Message"},required:[]},Schema:c,MessageExample:{properties:{payload:{isExample:!0},summary:{type:"string"},name:{type:"string"},headers:{type:"object"}}},SchemaProperties:{properties:{},additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema"},DiscriminatorMapping:u,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"]},Components:{properties:{messages:"NamedMessages",parameters:"NamedParameters",schemas:"NamedSchemas",correlationIds:"NamedCorrelationIds",messageTraits:"NamedMessageTraits",operationTraits:"NamedOperationTraits",streamHeaders:"NamedStreamHeaders",securitySchemes:"NamedSecuritySchemes",servers:"ServerMap",serverVariables:"ServerVariablesMap",channels:"ChannelMap",serverBindings:"ServerBindings",channelBindings:"ChannelBindings",operationBindings:"OperationBindings",messageBindings:"MessageBindings"}},NamedSchemas:(0,r.mapOf)("Schema"),NamedMessages:(0,r.mapOf)("Message"),NamedMessageTraits:(0,r.mapOf)("MessageTrait"),NamedOperationTraits:(0,r.mapOf)("OperationTrait"),NamedParameters:(0,r.mapOf)("Parameter"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),NamedCorrelationIds:(0,r.mapOf)("CorrelationId"),NamedStreamHeaders:(0,r.mapOf)("StreamHeader"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"]},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["authorizationUrl","tokenUrl","scopes"]},SecuritySchemeFlows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"}},SecurityScheme:p,Message:{properties:{messageId:{type:"string"},headers:"Schema",payload:"Schema",correlationId:"CorrelationId",schemaFormat:{type:"string"},contentType:{type:"string"},name:{type:"string"},title:{type:"string"},summary:{type:"string"},description:{type:"string"},tags:"TagList",externalDocs:"ExternalDocs",bindings:"MessageBindings",traits:"MessageTraitList"},additionalProperties:{}},MessageBindings:a,OperationBindings:l,OperationTrait:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},security:"SecurityRequirementList",bindings:"OperationBindings"},required:[]},OperationTraitList:(0,r.listOf)("OperationTrait"),MessageTrait:{properties:{messageId:{type:"string"},headers:"Schema",correlationId:"CorrelationId",schemaFormat:{type:"string"},contentType:{type:"string"},name:{type:"string"},title:{type:"string"},summary:{type:"string"},description:{type:"string"},tags:"TagList",externalDocs:"ExternalDocs",bindings:"MessageBindings"},additionalProperties:{}},MessageTraitList:(0,r.listOf)("MessageTrait"),CorrelationId:{properties:{description:{type:"string"},location:{type:"string"}},required:["location"]}}},1990:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNamedType=t.normalizeTypes=t.SpecExtension=t.mapOf=t.listOf=void 0,t.listOf=function(e){return{name:`${e}List`,properties:{},items:e}},t.mapOf=function(e){return{name:`${e}Map`,properties:{},additionalProperties:()=>e}},t.SpecExtension={name:"SpecExtension",properties:{},additionalProperties:{resolvable:!0}},t.normalizeTypes=function(e,n={}){const r={};for(const t of Object.keys(e))r[t]=Object.assign(Object.assign({},e[t]),{name:t});for(const e of Object.values(r))i(e);return r.SpecExtension=t.SpecExtension,r;function i(e){if(e.additionalProperties&&(e.additionalProperties=o(e.additionalProperties)),e.items&&(e.items=o(e.items)),e.properties){const t={};for(const[r,i]of Object.entries(e.properties))t[r]=o(i),n.doNotResolveExamples&&i&&i.isExample&&(t[r]=Object.assign(Object.assign({},i),{resolvable:!1}));e.properties=t}}function o(e){if("string"==typeof e){if(!r[e])throw new Error(`Unknown type name found: ${e}`);return r[e]}return"function"==typeof e?(t,n)=>o(e(t,n)):e&&e.name?(i(e=Object.assign({},e)),e):e&&e.directResolveAs?Object.assign(Object.assign({},e),{directResolveAs:o(e.directResolveAs)}):e}},t.isNamedType=function(e){return"string"==typeof(null==e?void 0:e.name)}},4409:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas2Types=void 0;const r=n(1990),i=/^[0-9][0-9Xx]{2}$/,o={properties:{name:{type:"string"},in:{type:"string",enum:["query","header","path","formData","body"]},description:{type:"string"},required:{type:"boolean"},schema:"Schema",type:{type:"string",enum:["string","number","integer","boolean","array","file"]},format:{type:"string"},allowEmptyValue:{type:"boolean"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"},"x-example":"Example","x-examples":"ExamplesMap"},required(e){return e&&e.in?"body"===e.in?["name","in","schema"]:"array"===e.type?["name","in","type","items"]:["name","in","type"]:["name","in"]},extensionsPrefix:"x-"},s={properties:{type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required(e){return e&&"array"===e.type?["type","items"]:["type"]},extensionsPrefix:"x-"},a={properties:{default:"Response"},additionalProperties:(e,t)=>i.test(t)?"Response":void 0},l={properties:{description:{type:"string"},schema:"Schema",headers:(0,r.mapOf)("Header"),examples:"Examples","x-summary":{type:"string"}},required:["description"],extensionsPrefix:"x-"},c={properties:{description:{type:"string"},type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required(e){return e&&"array"===e.type?["type","items"]:["type"]},extensionsPrefix:"x-"},u={properties:{format:{type:"string"},title:{type:"string"},description:{type:"string"},default:null,multipleOf:{type:"number"},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"number"},minLength:{type:"number"},pattern:{type:"string"},maxItems:{type:"number"},minItems:{type:"number"},uniqueItems:{type:"boolean"},maxProperties:{type:"number"},minProperties:{type:"number"},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{type:"string",enum:["object","array","string","number","integer","boolean","null"]},items:e=>Array.isArray(e)?(0,r.listOf)("Schema"):"Schema",allOf:(0,r.listOf)("Schema"),properties:"SchemaProperties",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",discriminator:{type:"string"},readOnly:{type:"boolean"},xml:"Xml",externalDocs:"ExternalDocs",example:{isExample:!0},"x-tags":{type:"array",items:{type:"string"}},"x-nullable":{type:"boolean"},"x-extendedDiscriminator":{type:"string"},"x-additionalPropertiesName":{type:"string"},"x-explicitMappingOnly":{type:"boolean"},"x-enumDescriptions":"EnumDescriptions"},extensionsPrefix:"x-"},p={properties:{type:{enum:["basic","apiKey","oauth2"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header"]},flow:{enum:["implicit","password","application","accessCode"]},authorizationUrl:{type:"string"},tokenUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},"x-defaultClientId":{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","scopes"];case"application":case"password":return["type","flow","tokenUrl","scopes"];default:return["type","flow","scopes"]}default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"basic":return["type","description"];case"apiKey":return["type","name","in","description"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","description","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","description","scopes"];case"application":case"password":return["type","flow","tokenUrl","description","scopes"];default:return["type","flow","tokenUrl","authorizationUrl","description","scopes"]}default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas2Types={Root:{properties:{swagger:{type:"string"},info:"Info",host:{type:"string"},basePath:{type:"string"},schemes:{type:"array",items:{type:"string"}},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},paths:"Paths",definitions:"NamedSchemas",parameters:"NamedParameters",responses:"NamedResponses",securityDefinitions:"NamedSecuritySchemes",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs","x-servers":"XServerList","x-tagGroups":"TagGroups","x-ignoredHeaderParameters":{type:"array",items:{type:"string"}}},required:["swagger","paths","info"],extensionsPrefix:"x-"},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs","x-traitTag":{type:"boolean"},"x-displayName":{type:"string"}},required:["name"],extensionsPrefix:"x-"},TagList:(0,r.listOf)("Tag"),TagGroups:(0,r.listOf)("TagGroup"),TagGroup:{properties:{name:{type:"string"},tags:{type:"array",items:{type:"string"}}}},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"],extensionsPrefix:"x-"},Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}},extensionsPrefix:"x-"},ExamplesMap:(0,r.mapOf)("Example"),EnumDescriptions:{properties:{},additionalProperties:{type:"string"}},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License",version:{type:"string"},"x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}},extensionsPrefix:"x-"},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Logo:{properties:{url:{type:"string"},altText:{type:"string"},backgroundColor:{type:"string"},href:{type:"string"}},extensionsPrefix:"x-"},Paths:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:{properties:{$ref:{type:"string"},parameters:"ParameterList",get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation"},extensionsPrefix:"x-"},Parameter:o,ParameterItems:s,ParameterList:(0,r.listOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},parameters:"ParameterList",responses:"Responses",schemes:{type:"array",items:{type:"string"}},deprecated:{type:"boolean"},security:"SecurityRequirementList","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},required:["responses"],extensionsPrefix:"x-"},Examples:{properties:{},additionalProperties:{isExample:!0}},Header:c,Responses:a,Response:l,Schema:u,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}},extensionsPrefix:"x-"},SchemaProperties:{properties:{},additionalProperties:"Schema"},NamedSchemas:(0,r.mapOf)("Schema"),NamedResponses:(0,r.mapOf)("Response"),NamedParameters:(0,r.mapOf)("Parameter"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),SecurityScheme:p,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},XCodeSampleList:(0,r.listOf)("XCodeSample"),XServerList:(0,r.listOf)("XServer"),XServer:{properties:{url:{type:"string"},description:{type:"string"}},required:["url"]}}},4154:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3Types=void 0;const r=n(1990),i=n(3873),o=/^[0-9][0-9Xx]{2}$/,s={properties:{default:"Response"},additionalProperties:(e,t)=>o.test(t)?"Response":void 0},a={properties:{externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",properties:"SchemaProperties",items:e=>Array.isArray(e)?(0,r.listOf)("Schema"):"Schema",additionalItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},default:null,nullable:{type:"boolean"},readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",example:{isExample:!0},deprecated:{type:"boolean"},"x-tags":{type:"array",items:{type:"string"}},"x-additionalPropertiesName":{type:"string"},"x-explicitMappingOnly":{type:"boolean"}},extensionsPrefix:"x-"},l={properties:{},additionalProperties:e=>(0,i.isMappingRef)(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},c={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"OAuth2Flows",openIdConnectUrl:{type:"string"},"x-defaultClientId":{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3Types={Root:{properties:{openapi:null,info:"Info",servers:"ServerList",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs",paths:"Paths",components:"Components","x-webhooks":"WebhooksMap","x-tagGroups":"TagGroups","x-ignoredHeaderParameters":{type:"array",items:{type:"string"}}},required:["openapi","paths","info"],extensionsPrefix:"x-"},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs","x-traitTag":{type:"boolean"},"x-displayName":{type:"string"}},required:["name"],extensionsPrefix:"x-"},TagList:(0,r.listOf)("Tag"),TagGroups:(0,r.listOf)("TagGroup"),TagGroup:{properties:{name:{type:"string"},tags:{type:"array",items:{type:"string"}}},extensionsPrefix:"x-"},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"],extensionsPrefix:"x-"},Server:{properties:{url:{type:"string"},description:{type:"string"},variables:"ServerVariablesMap"},required:["url"],extensionsPrefix:"x-"},ServerList:(0,r.listOf)("Server"),ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:{type:"string"}},required:["default"],extensionsPrefix:"x-"},ServerVariablesMap:(0,r.mapOf)("ServerVariable"),SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License","x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}},extensionsPrefix:"x-"},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Paths:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:{properties:{$ref:{type:"string"},servers:"ServerList",parameters:"ParameterList",summary:{type:"string"},description:{type:"string"},get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation",trace:"Operation"},extensionsPrefix:"x-"},Parameter:{properties:{name:{type:"string"},in:{enum:["query","header","path","cookie"]},description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",content:"MediaTypesMap"},required:["name","in"],requiredOneOf:["schema","content"],extensionsPrefix:"x-"},ParameterList:(0,r.listOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:"ParameterList",security:"SecurityRequirementList",servers:"ServerList",requestBody:"RequestBody",responses:"Responses",deprecated:{type:"boolean"},callbacks:"CallbacksMap","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},required:["responses"],extensionsPrefix:"x-"},Callback:(0,r.mapOf)("PathItem"),CallbacksMap:(0,r.mapOf)("Callback"),RequestBody:{properties:{description:{type:"string"},required:{type:"boolean"},content:"MediaTypesMap"},required:["content"],extensionsPrefix:"x-"},MediaTypesMap:{properties:{},additionalProperties:"MediaType"},MediaType:{properties:{schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",encoding:"EncodingMap"},extensionsPrefix:"x-"},Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}},extensionsPrefix:"x-"},ExamplesMap:(0,r.mapOf)("Example"),Encoding:{properties:{contentType:{type:"string"},headers:"HeadersMap",style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"}},extensionsPrefix:"x-"},EncodingMap:(0,r.mapOf)("Encoding"),EnumDescriptions:{properties:{},additionalProperties:{type:"string"}},Header:{properties:{description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",content:"MediaTypesMap"},requiredOneOf:["schema","content"],extensionsPrefix:"x-"},HeadersMap:(0,r.mapOf)("Header"),Responses:s,Response:{properties:{description:{type:"string"},headers:"HeadersMap",content:"MediaTypesMap",links:"LinksMap","x-summary":{type:"string"}},required:["description"],extensionsPrefix:"x-"},Link:{properties:{operationRef:{type:"string"},operationId:{type:"string"},parameters:null,requestBody:null,description:{type:"string"},server:"Server"},extensionsPrefix:"x-"},Logo:{properties:{url:{type:"string"},altText:{type:"string"},backgroundColor:{type:"string"},href:{type:"string"}}},Schema:a,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}},extensionsPrefix:"x-"},SchemaProperties:{properties:{},additionalProperties:"Schema"},DiscriminatorMapping:l,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"],extensionsPrefix:"x-"},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks"},extensionsPrefix:"x-"},LinksMap:(0,r.mapOf)("Link"),NamedSchemas:(0,r.mapOf)("Schema"),NamedResponses:(0,r.mapOf)("Response"),NamedParameters:(0,r.mapOf)("Parameter"),NamedExamples:(0,r.mapOf)("Example"),NamedRequestBodies:(0,r.mapOf)("RequestBody"),NamedHeaders:(0,r.mapOf)("Header"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),NamedLinks:(0,r.mapOf)("Link"),NamedCallbacks:(0,r.mapOf)("Callback"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"],extensionsPrefix:"x-"},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"],extensionsPrefix:"x-"},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"],extensionsPrefix:"x-"},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"},"x-usePkce":e=>"boolean"==typeof e?{type:"boolean"}:"XUsePkce"},required:["authorizationUrl","tokenUrl","scopes"],extensionsPrefix:"x-"},OAuth2Flows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"},extensionsPrefix:"x-"},SecurityScheme:c,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},XCodeSampleList:(0,r.listOf)("XCodeSample"),XUsePkce:{properties:{disableManualConfiguration:{type:"boolean"},hideClientSecretInput:{type:"boolean"}}},WebhooksMap:{properties:{},additionalProperties:()=>"PathItem"}}},2082:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3_1Types=void 0;const r=n(1990),i=n(4154),o={properties:{$id:{type:"string"},$anchor:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:(0,r.listOf)("Schema"),prefixItems:(0,r.listOf)("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},"x-tags":{type:"array",items:{type:"string"}}},extensionsPrefix:"x-"},s={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect","mutualTLS"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"OAuth2Flows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":switch(null==e?void 0:e.flows){case"implicit":return["type","flows","authorizationUrl","refreshUrl","description","scopes"];case"password":case"clientCredentials":return["type","flows","tokenUrl","refreshUrl","description","scopes"];default:return["type","flows","authorizationUrl","refreshUrl","tokenUrl","description","scopes"]}case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3_1Types=Object.assign(Object.assign({},i.Oas3Types),{Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},summary:{type:"string"},contact:"Contact",license:"License","x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Root:{properties:{openapi:null,info:"Info",servers:"ServerList",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs",paths:"Paths",webhooks:"WebhooksMap",components:"Components",jsonSchemaDialect:{type:"string"}},required:["openapi","info"],requiredOneOf:["paths","components","webhooks"],extensionsPrefix:"x-"},Schema:o,License:{properties:{name:{type:"string"},url:{type:"string"},identifier:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks",pathItems:"NamedPathItems"},extensionsPrefix:"x-"},NamedPathItems:(0,r.mapOf)("PathItem"),SecurityScheme:s,Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:"ParameterList",security:"SecurityRequirementList",servers:"ServerList",requestBody:"RequestBody",responses:"Responses",deprecated:{type:"boolean"},callbacks:"CallbacksMap","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},extensionsPrefix:"x-"}})},8209:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.nextTick=t.pickDefined=t.keysOf=t.identity=t.isTruthy=t.showErrorForDeprecatedField=t.showWarningForDeprecatedField=t.doesYamlFileExist=t.isCustomRuleId=t.getMatchingStatusCodeRange=t.assignExisting=t.isNotString=t.isString=t.isNotEmptyObject=t.slash=t.isPathParameter=t.yamlAndJsonSyncReader=t.readFileAsStringSync=t.isSingular=t.validateMimeTypeOAS3=t.validateMimeType=t.splitCamelCaseIntoWords=t.omitObjectProps=t.pickObjectProps=t.readFileFromUrl=t.isEmptyArray=t.isEmptyObject=t.isPlainObject=t.isDefined=t.loadYaml=t.popStack=t.pushStack=t.stringifyYaml=t.parseYaml=void 0;const i=n(7411),o=n(7975),s=n(4536),a=n(8381),l=n(5127),c=n(970),u=n(1827),p=n(2678);var d=n(970);function f(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function h(e,t){return t.match(/^https?:\/\//)||(e=e.replace(/^https?:\/\//,"")),s(e,t)}function m(e){return"string"==typeof e}function g(e){return!!e}function y(e,t){return`${void 0!==t?`${t}.`:""}${e}`}Object.defineProperty(t,"parseYaml",{enumerable:!0,get:function(){return d.parseYaml}}),Object.defineProperty(t,"stringifyYaml",{enumerable:!0,get:function(){return d.stringifyYaml}}),t.pushStack=function(e,t){return{prev:e,value:t}},t.popStack=function(e){var t;return null!==(t=null==e?void 0:e.prev)&&void 0!==t?t:null},t.loadYaml=function(e){return r(this,void 0,void 0,(function*(){const t=yield i.promises.readFile(e,"utf-8");return(0,c.parseYaml)(t)}))},t.isDefined=function(e){return void 0!==e},t.isPlainObject=f,t.isEmptyObject=function(e){return f(e)&&0===Object.keys(e).length},t.isEmptyArray=function(e){return Array.isArray(e)&&0===e.length},t.readFileFromUrl=function(e,t){return r(this,void 0,void 0,(function*(){const n={};for(const r of t.headers)h(e,r.matches)&&(n[r.name]=void 0!==r.envVariable?u.env[r.envVariable]||"":r.value);const r=yield(t.customFetch||a.default)(e,{headers:n});if(!r.ok)throw new Error(`Failed to load ${e}: ${r.status} ${r.statusText}`);return{body:yield r.text(),mimeType:r.headers.get("content-type")}}))},t.pickObjectProps=function(e,t){return Object.fromEntries(t.filter((t=>t in e)).map((t=>[t,e[t]])))},t.omitObjectProps=function(e,t){return Object.fromEntries(Object.entries(e).filter((([e])=>!t.includes(e))))},t.splitCamelCaseIntoWords=function(e){const t=e.split(/(?:[-._])|([A-Z][a-z]+)/).filter(g).map((e=>e.toLocaleLowerCase())),n=e.split(/([A-Z]{2,})/).filter((e=>e&&e===e.toUpperCase())).map((e=>e.toLocaleLowerCase()));return new Set([...t,...n])},t.validateMimeType=function({type:e,value:t},{report:n,location:r},i){if(!i)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t[e])for(const o of t[e])i.includes(o)||n({message:`Mime type "${o}" is not allowed`,location:r.child(t[e].indexOf(o)).key()})},t.validateMimeTypeOAS3=function({type:e,value:t},{report:n,location:r},i){if(!i)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t.content)for(const e of Object.keys(t.content))i.includes(e)||n({message:`Mime type "${e}" is not allowed`,location:r.child("content").child(e).key()})},t.isSingular=function(e){return l.isSingular(e)},t.readFileAsStringSync=function(e){return i.readFileSync(e,"utf-8")},t.yamlAndJsonSyncReader=function(e){const t=i.readFileSync(e,"utf-8");return(0,c.parseYaml)(t)},t.isPathParameter=function(e){return e.startsWith("{")&&e.endsWith("}")},t.slash=function(e){return/^\\\\\?\\/.test(e)?e:e.replace(/\\/g,"/")},t.isNotEmptyObject=function(e){return!!e&&Object.keys(e).length>0},t.isString=m,t.isNotString=function(e){return!m(e)},t.assignExisting=function(e,t){for(const n of Object.keys(t))e.hasOwnProperty(n)&&(e[n]=t[n])},t.getMatchingStatusCodeRange=function(e){return`${e}`.replace(/^(\d)\d\d$/,((e,t)=>`${t}XX`))},t.isCustomRuleId=function(e){return e.includes("/")},t.doesYamlFileExist=function(e){return(".yaml"===(0,o.extname)(e)||".yml"===(0,o.extname)(e))&&i.hasOwnProperty("existsSync")&&i.existsSync(e)},t.showWarningForDeprecatedField=function(e,t,n){p.logger.warn(`The '${p.colorize.red(e)}' field is deprecated. ${t?`Use ${p.colorize.green(y(t,n))} instead. `:""}Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`)},t.showErrorForDeprecatedField=function(e,t,n){throw new Error(`Do not use '${e}' field. ${t?`Use '${y(t,n)}' instead. `:""}\n`)},t.isTruthy=g,t.identity=function(e){return e},t.keysOf=function(e){return e?Object.keys(e):[]},t.pickDefined=function(e){if(!e)return;const t={};for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t},t.nextTick=function(){new Promise((e=>{setTimeout(e)}))}},2161:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizeVisitors=void 0;const r=n(1990),i={Root:"DefinitionRoot",ServerVariablesMap:"ServerVariableMap",Paths:["PathMap","PathsMap"],CallbacksMap:"CallbackMap",MediaTypesMap:"MediaTypeMap",ExamplesMap:"ExampleMap",EncodingMap:"EncodingsMap",HeadersMap:"HeaderMap",LinksMap:"LinkMap",OAuth2Flows:"SecuritySchemeFlows",Responses:"ResponsesMap"};t.normalizeVisitors=function(e,t){const n={any:{enter:[],leave:[]}};for(const e of Object.keys(t))n[e]={enter:[],leave:[]};n.ref={enter:[],leave:[]};for(const{ruleId:t,severity:n,visitor:r}of e)a({ruleId:t,severity:n},r,null);for(const e of Object.keys(n))n[e].enter.sort(((e,t)=>t.depth-e.depth)),n[e].leave.sort(((e,t)=>e.depth-t.depth));return n;function o(e,t,i,s,a=[]){if(a.includes(t))return;a=[...a,t];const l=new Set;for(const n of Object.values(t.properties))n!==i?"object"==typeof n&&null!==n&&n.name&&l.add(n):c(e,a);t.additionalProperties&&"function"!=typeof t.additionalProperties&&(t.additionalProperties===i?c(e,a):void 0!==t.additionalProperties.name&&l.add(t.additionalProperties)),t.items&&(t.items===i?c(e,a):void 0!==t.items.name&&l.add(t.items)),t.extensionsPrefix&&l.add(r.SpecExtension);for(const t of Array.from(l.values()))o(e,t,i,s,a);function c(e,t){for(const r of t.slice(1))n[r.name]=n[r.name]||{enter:[],leave:[]},n[r.name].enter.push(Object.assign(Object.assign({},e),{visit:()=>{},depth:0,context:{isSkippedLevel:!0,seen:new Set,parent:s}}))}}function s(e,t){if(Array.isArray(t)){const n=t.find((t=>e[t]))||void 0;return n&&e[n]}return e[t]}function a(e,r,l,c=0){const u=Object.keys(t);if(0===c)u.push("any"),u.push("ref");else{if(r.any)throw new Error("any() is allowed only on top level");if(r.ref)throw new Error("ref() is allowed only on top level")}for(const p of u){const u=r[p]||s(r,i[p]),d=n[p];if(!u)continue;let f,h,m;const g="object"==typeof u;if("ref"===p&&g&&u.skip)throw new Error("ref() visitor does not support skip");"function"==typeof u?f=u:g&&(f=u.enter,h=u.leave,m=u.skip);const y={activatedOn:null,type:t[p],parent:l,isSkippedLevel:!1};if("object"==typeof u&&a(e,u,y,c+1),l&&o(e,l.type,t[p],l),f||g){if(f&&"function"!=typeof f)throw new Error("DEV: should be function");d.enter.push(Object.assign(Object.assign({},e),{visit:f||(()=>{}),skip:m,depth:c,context:y}))}if(h){if("function"!=typeof h)throw new Error("DEV: should be function");d.leave.push(Object.assign(Object.assign({},e),{visit:h,depth:c,context:y}))}}}}},5735:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.walkDocument=void 0;const r=n(3873),i=n(8209),o=n(2928),s=n(1990);function a(e){var t,n;const r={};for(;e.parent;)(null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.location)&&(r[e.parent.type.name]=null===(n=e.parent.activatedOn)||void 0===n?void 0:n.value.location),e=e.parent;return r}t.walkDocument=function(e){const{document:t,rootType:n,normalizedVisitors:l,resolvedRefMap:c,ctx:u}=e,p={},d=new Set;!function e(t,n,f,h,m){var g,y,b,v,x,w,k,S,E,O,_;const A=(e,t=j.source.absoluteRef)=>{if(!(0,r.isRef)(e))return{location:f,node:e};const n=(0,o.makeRefId)(t,e.$ref),i=c.get(n);if(!i)return{location:void 0,node:void 0};const{resolved:s,node:a,document:l,nodePointer:u,error:p}=i;return{location:s?new r.Location(l.source,u):p instanceof o.YamlParseError?new r.Location(p.source,""):void 0,node:a,error:p}},C=f;let j=f;const{node:P,location:T,error:I}=A(t),R=new Set;if((0,r.isRef)(t)){const e=l.ref.enter;for(const{visit:r,ruleId:i,severity:o,context:s}of e)R.add(s),r(t,{report:$.bind(void 0,i,o),resolve:A,rawNode:t,rawLocation:C,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:L.bind(void 0,i)},{node:P,location:T,error:I}),(null==T?void 0:T.source.absoluteRef)&&u.refTypes&&u.refTypes.set(null==T?void 0:T.source.absoluteRef,n)}if(void 0!==P&&T&&"scalar"!==n.name){j=T;const o=null===(y=null===(g=p[n.name])||void 0===g?void 0:g.has)||void 0===y?void 0:y.call(g,P);let a=!1;const c=l.any.enter.concat((null===(b=l[n.name])||void 0===b?void 0:b.enter)||[]),u=[];for(const{context:e,visit:r,skip:s,ruleId:l,severity:p}of c){if(d.has(j.pointer))break;if(e.isSkippedLevel)!e.parent.activatedOn||e.parent.activatedOn.value.nextLevelTypeActivated||e.seen.has(t)||(e.seen.add(t),a=!0,u.push(e));else if(e.parent&&e.parent.activatedOn&&(null===(v=e.activatedOn)||void 0===v?void 0:v.value.withParentNode)!==e.parent.activatedOn.value.node&&(null===(x=e.parent.activatedOn.value.nextLevelTypeActivated)||void 0===x?void 0:x.value)!==n||!e.parent&&!o){u.push(e);const o={node:P,location:T,nextLevelTypeActivated:null,withParentNode:null===(k=null===(w=e.parent)||void 0===w?void 0:w.activatedOn)||void 0===k?void 0:k.value.node,skipped:null!==(O=(null===(E=null===(S=e.parent)||void 0===S?void 0:S.activatedOn)||void 0===E?void 0:E.value.skipped)||(null==s?void 0:s(P,m,{location:f,rawLocation:C,resolve:A,rawNode:t})))&&void 0!==O&&O};e.activatedOn=(0,i.pushStack)(e.activatedOn,o);let c=e.parent;for(;c;)c.activatedOn.value.nextLevelTypeActivated=(0,i.pushStack)(c.activatedOn.value.nextLevelTypeActivated,n),c=c.parent;o.skipped||(a=!0,R.add(e),N(r,P,t,e,l,p))}}if(a||!o)if(p[n.name]=p[n.name]||new Set,p[n.name].add(P),Array.isArray(P)){const t=n.items;if(void 0!==t)for(let n=0;n!i.includes(e)))):n.extensionsPrefix&&i.push(...Object.keys(P).filter((e=>e.startsWith(n.extensionsPrefix)))),(0,r.isRef)(t)&&i.push(...Object.keys(t).filter((e=>"$ref"!==e&&!i.includes(e))));for(const o of i){let i=P[o],a=T;void 0===i&&(i=t[o],a=f);let l=n.properties[o];void 0===l&&(l=n.additionalProperties),"function"==typeof l&&(l=l(i,o)),void 0===l&&n.extensionsPrefix&&o.startsWith(n.extensionsPrefix)&&(l=s.SpecExtension),!(0,s.isNamedType)(l)&&(null==l?void 0:l.directResolveAs)&&(l=l.directResolveAs,i={$ref:i}),l&&void 0===l.name&&!1!==l.resolvable&&(l={name:"scalar",properties:{}}),(0,s.isNamedType)(l)&&("scalar"!==l.name||(0,r.isRef)(i))&&e(i,l,a.child([o]),P,o)}}const h=l.any.leave,I=((null===(_=l[n.name])||void 0===_?void 0:_.leave)||[]).concat(h);for(const e of u.reverse())if(e.isSkippedLevel)e.seen.delete(P);else if(e.activatedOn=(0,i.popStack)(e.activatedOn),e.parent){let t=e.parent;for(;t;)t.activatedOn.value.nextLevelTypeActivated=(0,i.popStack)(t.activatedOn.value.nextLevelTypeActivated),t=t.parent}for(const{context:e,visit:n,ruleId:r,severity:i}of I)!e.isSkippedLevel&&R.has(e)&&N(n,P,t,e,r,i)}if(j=f,(0,r.isRef)(t)){const e=l.ref.leave;for(const{visit:r,ruleId:i,severity:o,context:s}of e)R.has(s)&&r(t,{report:$.bind(void 0,i,o),resolve:A,rawNode:t,rawLocation:C,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:L.bind(void 0,i)},{node:P,location:T,error:I})}function N(e,t,r,i,o,s){e(t,{report:$.bind(void 0,o,s),resolve:A,rawNode:r,location:j,rawLocation:C,type:n,parent:h,key:m,parentLocations:a(i),oasVersion:u.oasVersion,ignoreNextVisitorsOnNode:()=>{d.add(j.pointer)},getVisitorData:L.bind(void 0,o)},function(e){var t;const n={};for(;e.parent;)n[e.parent.type.name]=null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.node,e=e.parent;return n}(i),i)}function $(e,t,n){const r=(n.location?Array.isArray(n.location)?n.location:[n.location]:[Object.assign(Object.assign({},j),{reportOnKey:!1})]).map((e=>Object.assign(Object.assign(Object.assign({},j),{reportOnKey:!1}),e))),i=n.forceSeverity||t;"off"!==i&&u.problems.push(Object.assign(Object.assign({ruleId:n.ruleId||e,severity:i},n),{suggest:n.suggest||[],location:r}))}function L(e){return u.visitorsData[e]=u.visitorsData[e]||{},u.visitorsData[e]}}(t.parsed,n,new r.Location(t.source,"#/"),void 0,"")}},1431:function(e,t,n){var r=n(8505);e.exports=function(e){return e?("{}"===e.substr(0,2)&&(e="\\{\\}"+e.substr(2)),g(function(e){return e.split("\\\\").join(i).split("\\{").join(o).split("\\}").join(s).split("\\,").join(a).split("\\.").join(l)}(e),!0).map(u)):[]};var i="\0SLASH"+Math.random()+"\0",o="\0OPEN"+Math.random()+"\0",s="\0CLOSE"+Math.random()+"\0",a="\0COMMA"+Math.random()+"\0",l="\0PERIOD"+Math.random()+"\0";function c(e){return parseInt(e,10)==e?parseInt(e,10):e.charCodeAt(0)}function u(e){return e.split(i).join("\\").split(o).join("{").split(s).join("}").split(a).join(",").split(l).join(".")}function p(e){if(!e)return[""];var t=[],n=r("{","}",e);if(!n)return e.split(",");var i=n.pre,o=n.body,s=n.post,a=i.split(",");a[a.length-1]+="{"+o+"}";var l=p(s);return s.length&&(a[a.length-1]+=l.shift(),a.push.apply(a,l)),t.push.apply(t,a),t}function d(e){return"{"+e+"}"}function f(e){return/^-?0\d/.test(e)}function h(e,t){return e<=t}function m(e,t){return e>=t}function g(e,t){var n=[],i=r("{","}",e);if(!i)return[e];var o=i.pre,a=i.post.length?g(i.post,!1):[""];if(/\$$/.test(i.pre))for(var l=0;l=0;if(!w&&!k)return i.post.match(/,(?!,).*\}/)?g(e=i.pre+"{"+i.body+s+i.post):[e];if(w)y=i.body.split(/\.\./);else if(1===(y=p(i.body)).length&&1===(y=g(y[0],!1).map(d)).length)return a.map((function(e){return i.pre+y[0]+e}));if(w){var S=c(y[0]),E=c(y[1]),O=Math.max(y[0].length,y[1].length),_=3==y.length?Math.abs(c(y[2])):1,A=h;E0){var I=new Array(T+1).join("0");P=j<0?"-"+I+P.slice(1):I+P}}b.push(P)}}else{b=[];for(var R=0;R(g(t),!(!n.nocomment&&"#"===t.charAt(0))&&new v(t,n).match(e));e.exports=r;const i=n(4077);r.sep=i.sep;const o=Symbol("globstar **");r.GLOBSTAR=o;const s=n(1431),a={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},l="[^/]",c=l+"*?",u=e=>e.split("").reduce(((e,t)=>(e[t]=!0,e)),{}),p=u("().*{}+?[]^$\\!"),d=u("[.("),f=/\/+/;r.filter=(e,t={})=>(n,i,o)=>r(n,e,t);const h=(e,t={})=>{const n={};return Object.keys(e).forEach((t=>n[t]=e[t])),Object.keys(t).forEach((e=>n[e]=t[e])),n};r.defaults=e=>{if(!e||"object"!=typeof e||!Object.keys(e).length)return r;const t=r,n=(n,r,i)=>t(n,r,h(e,i));return(n.Minimatch=class extends t.Minimatch{constructor(t,n){super(t,h(e,n))}}).defaults=n=>t.defaults(h(e,n)).Minimatch,n.filter=(n,r)=>t.filter(n,h(e,r)),n.defaults=n=>t.defaults(h(e,n)),n.makeRe=(n,r)=>t.makeRe(n,h(e,r)),n.braceExpand=(n,r)=>t.braceExpand(n,h(e,r)),n.match=(n,r,i)=>t.match(n,r,h(e,i)),n},r.braceExpand=(e,t)=>m(e,t);const m=(e,t={})=>(g(e),t.nobrace||!/\{(?:(?!\{).)*\}/.test(e)?[e]:s(e)),g=e=>{if("string"!=typeof e)throw new TypeError("invalid pattern");if(e.length>65536)throw new TypeError("pattern is too long")},y=Symbol("subparse");r.makeRe=(e,t)=>new v(e,t||{}).makeRe(),r.match=(e,t,n={})=>{const r=new v(t,n);return e=e.filter((e=>r.match(e))),r.options.nonull&&!e.length&&e.push(t),e};const b=e=>e.replace(/[[\]\\]/g,"\\$&");class v{constructor(e,t){g(e),t||(t={}),this.options=t,this.set=[],this.pattern=e,this.windowsPathsNoEscape=!!t.windowsPathsNoEscape||!1===t.allowWindowsEscape,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.partial=!!t.partial,this.make()}debug(){}make(){const e=this.pattern,t=this.options;if(!t.nocomment&&"#"===e.charAt(0))return void(this.comment=!0);if(!e)return void(this.empty=!0);this.parseNegate();let n=this.globSet=this.braceExpand();t.debug&&(this.debug=(...e)=>console.error(...e)),this.debug(this.pattern,n),n=this.globParts=n.map((e=>e.split(f))),this.debug(this.pattern,n),n=n.map(((e,t,n)=>e.map(this.parse,this))),this.debug(this.pattern,n),n=n.filter((e=>-1===e.indexOf(!1))),this.debug(this.pattern,n),this.set=n}parseNegate(){if(this.options.nonegate)return;const e=this.pattern;let t=!1,n=0;for(let r=0;r>> no match, partial?",e,d,t,f),d!==a))}if("string"==typeof u?(c=p===u,this.debug("string match",u,p,c)):(c=p.match(u),this.debug("pattern match",u,p,c)),!c)return!1}if(i===a&&s===l)return!0;if(i===a)return n;if(s===l)return i===a-1&&""===e[i];throw new Error("wtf?")}braceExpand(){return m(this.pattern,this.options)}parse(e,t){g(e);const n=this.options;if("**"===e){if(!n.noglobstar)return o;e="*"}if(""===e)return"";let r="",i=!1,s=!1;const u=[],f=[];let h,m,v,x,w=!1,k=-1,S=-1,E="."===e.charAt(0),O=n.dot||E;const _=e=>"."===e.charAt(0)?"":n.dot?"(?!(?:^|\\/)\\.{1,2}(?:$|\\/))":"(?!\\.)",A=()=>{if(h){switch(h){case"*":r+=c,i=!0;break;case"?":r+=l,i=!0;break;default:r+="\\"+h}this.debug("clearStateChar %j %j",h,r),h=!1}};for(let t,o=0;o(n||(n="\\"),t+t+n+"|"))),this.debug("tail=%j\n %s",e,e,v,r);const t="*"===v.type?c:"?"===v.type?l:"\\"+v.type;i=!0,r=r.slice(0,v.reStart)+t+"\\("+e}A(),s&&(r+="\\\\");const C=d[r.charAt(0)];for(let e=f.length-1;e>-1;e--){const n=f[e],i=r.slice(0,n.reStart),o=r.slice(n.reStart,n.reEnd-8);let s=r.slice(n.reEnd);const a=r.slice(n.reEnd-8,n.reEnd)+s,l=i.split(")").length,c=i.split("(").length-l;let u=s;for(let e=0;ee.replace(/\\(.)/g,"$1"))(e);const j=n.nocase?"i":"";try{return Object.assign(new RegExp("^"+r+"$",j),{_glob:e,_src:r})}catch(e){return new RegExp("$.")}}makeRe(){if(this.regexp||!1===this.regexp)return this.regexp;const e=this.set;if(!e.length)return this.regexp=!1,this.regexp;const t=this.options,n=t.noglobstar?c:t.dot?"(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?":"(?:(?!(?:\\/|^)\\.).)*?",r=t.nocase?"i":"";let i=e.map((e=>(e=e.map((e=>"string"==typeof e?e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"):e===o?o:e._src)).reduce(((e,t)=>(e[e.length-1]===o&&t===o||e.push(t),e)),[]),e.forEach(((t,r)=>{t===o&&e[r-1]!==o&&(0===r?e.length>1?e[r+1]="(?:\\/|"+n+"\\/)?"+e[r+1]:e[r]=n:r===e.length-1?e[r-1]+="(?:\\/|"+n+")?":(e[r-1]+="(?:\\/|\\/"+n+"\\/)"+e[r+1],e[r+1]=o))})),e.filter((e=>e!==o)).join("/")))).join("|");i="^(?:"+i+")$",this.negate&&(i="^(?!"+i+").*$");try{this.regexp=new RegExp(i,r)}catch(e){this.regexp=!1}return this.regexp}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return""===e;if("/"===e&&t)return!0;const n=this.options;"/"!==i.sep&&(e=e.split(i.sep).join("/")),e=e.split(f),this.debug(this.pattern,"split",e);const r=this.set;let o;this.debug(this.pattern,"set",r);for(let t=e.length-1;t>=0&&(o=e[t],!o);t--);for(let i=0;i=0&&c>0){if(e===t)return[l,c];for(r=[],o=n.length;u>=0&&!a;)u==l?(r.push(u),l=n.indexOf(e,u+1)):1==r.length?a=[r.pop(),c]:((i=r.pop())=0?l:c;r.length&&(a=[o,s])}return a}e.exports=t,t.range=r},3998:function(e,t,n){"use strict";var r=n(1137);e.exports=function(e,t){return e?void t.then((function(t){r((function(){e(null,t)}))}),(function(t){r((function(){e(t)}))})):t}},1137:function(e){"use strict";e.exports="object"==typeof process&&"function"==typeof process.nextTick?process.nextTick:"function"==typeof setImmediate?setImmediate:function(e){setTimeout(e,0)}},2485:function(e,t){var n;!function(){"use strict";var r={}.hasOwnProperty;function i(){for(var e=[],t=0;tu;)if((a=l[u++])!=a)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:s(!0),indexOf:s(!1)}},1344:function(e,t,n){var r=n(6885),i=n(8664),o=n(2612),s=n(3747),a=n(2998),l=[].push,c=function(e){var t=1==e,n=2==e,c=3==e,u=4==e,p=6==e,d=7==e,f=5==e||p;return function(h,m,g,y){for(var b,v,x=o(h),w=i(x),k=r(m,g,3),S=s(w.length),E=0,O=y||a,_=t?O(h,S):n||d?O(h,0):void 0;S>E;E++)if((f||E in w)&&(v=k(b=w[E],E,x),e))if(t)_[E]=v;else if(v)switch(e){case 3:return!0;case 5:return b;case 6:return E;case 2:l.call(_,b)}else switch(e){case 4:return!1;case 7:l.call(_,b)}return p?-1:c||u?u:_}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},5634:function(e,t,n){var r=n(2074),i=n(1602),o=n(6845),s=i("species");e.exports=function(e){return o>=51||!r((function(){var t=[];return(t.constructor={})[s]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},2998:function(e,t,n){var r=n(5335),i=n(8679),o=n(1602)("species");e.exports=function(e,t){var n;return i(e)&&("function"!=typeof(n=e.constructor)||n!==Array&&!i(n.prototype)?r(n)&&null===(n=n[o])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===t?0:t)}},8569:function(e){var t={}.toString;e.exports=function(e){return t.call(e).slice(8,-1)}},3062:function(e,t,n){var r=n(3129),i=n(8569),o=n(1602)("toStringTag"),s="Arguments"==i(function(){return arguments}());e.exports=r?i:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?n:s?i(t):"Object"==(r=i(t))&&"function"==typeof t.callee?"Arguments":r}},4361:function(e,t,n){var r=n(1883),i=n(5816),o=n(7632),s=n(3610);e.exports=function(e,t){for(var n=i(t),a=s.f,l=o.f,c=0;c=74)&&(r=s.match(/Chrome\/(\d+)/))&&(i=r[1]),e.exports=i&&+i},290:function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},1605:function(e,t,n){var r=n(200),i=n(7632).f,o=n(7712),s=n(7485),a=n(5975),l=n(4361),c=n(4977);e.exports=function(e,t){var n,u,p,d,f,h=e.target,m=e.global,g=e.stat;if(n=m?r:g?r[h]||a(h,{}):(r[h]||{}).prototype)for(u in t){if(d=t[u],p=e.noTargetGet?(f=i(n,u))&&f.value:n[u],!c(m?u:h+(g?".":"#")+u,e.forced)&&void 0!==p){if(typeof d==typeof p)continue;l(d,p)}(e.sham||p&&p.sham)&&o(d,"sham",!0),s(n,u,d,e)}}},2074:function(e){e.exports=function(e){try{return!!e()}catch(e){return!0}}},6885:function(e,t,n){var r=n(9085);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},6492:function(e,t,n){var r=n(9720),i=n(200),o=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?o(r[e])||o(i[e]):r[e]&&r[e][t]||i[e]&&i[e][t]}},200:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},1883:function(e,t,n){var r=n(2612),i={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return i.call(r(e),t)}},7708:function(e){e.exports={}},8890:function(e,t,n){var r=n(6492);e.exports=r("document","documentElement")},7694:function(e,t,n){var r=n(5077),i=n(2074),o=n(3262);e.exports=!r&&!i((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},8664:function(e,t,n){var r=n(2074),i=n(8569),o="".split;e.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==i(e)?o.call(e,""):Object(e)}:Object},9965:function(e,t,n){var r=n(9310),i=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return i.call(e)}),e.exports=r.inspectSource},9206:function(e,t,n){var r,i,o,s=n(2886),a=n(200),l=n(5335),c=n(7712),u=n(1883),p=n(9310),d=n(5904),f=n(7708),h="Object already initialized",m=a.WeakMap;if(s||p.state){var g=p.state||(p.state=new m),y=g.get,b=g.has,v=g.set;r=function(e,t){if(b.call(g,e))throw new TypeError(h);return t.facade=e,v.call(g,e,t),t},i=function(e){return y.call(g,e)||{}},o=function(e){return b.call(g,e)}}else{var x=d("state");f[x]=!0,r=function(e,t){if(u(e,x))throw new TypeError(h);return t.facade=e,c(e,x,t),t},i=function(e){return u(e,x)?e[x]:{}},o=function(e){return u(e,x)}}e.exports={set:r,get:i,has:o,enforce:function(e){return o(e)?i(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=i(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},8679:function(e,t,n){var r=n(8569);e.exports=Array.isArray||function(e){return"Array"==r(e)}},4977:function(e,t,n){var r=n(2074),i=/#|\.prototype\./,o=function(e,t){var n=a[s(e)];return n==c||n!=l&&("function"==typeof t?r(t):!!t)},s=o.normalize=function(e){return String(e).replace(i,".").toLowerCase()},a=o.data={},l=o.NATIVE="N",c=o.POLYFILL="P";e.exports=o},5335:function(e){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},6926:function(e){e.exports=!1},1849:function(e,t,n){var r=n(6845),i=n(2074);e.exports=!!Object.getOwnPropertySymbols&&!i((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},2886:function(e,t,n){var r=n(200),i=n(9965),o=r.WeakMap;e.exports="function"==typeof o&&/native code/.test(i(o))},3105:function(e,t,n){var r,i=n(3938),o=n(5318),s=n(290),a=n(7708),l=n(8890),c=n(3262),u=n(5904),p="prototype",d="script",f=u("IE_PROTO"),h=function(){},m=function(e){return"<"+d+">"+e+""},g=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t,n;g=r?function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t}(r):(t=c("iframe"),n="java"+d+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F);for(var i=s.length;i--;)delete g[p][s[i]];return g()};a[f]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(h[p]=i(e),n=new h,h[p]=null,n[f]=e):n=g(),void 0===t?n:o(n,t)}},5318:function(e,t,n){var r=n(5077),i=n(3610),o=n(3938),s=n(1641);e.exports=r?Object.defineProperties:function(e,t){o(e);for(var n,r=s(t),a=r.length,l=0;a>l;)i.f(e,n=r[l++],t[n]);return e}},3610:function(e,t,n){var r=n(5077),i=n(7694),o=n(3938),s=n(874),a=Object.defineProperty;t.f=r?a:function(e,t,n){if(o(e),t=s(t,!0),o(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},7632:function(e,t,n){var r=n(5077),i=n(9304),o=n(6843),s=n(5476),a=n(874),l=n(1883),c=n(7694),u=Object.getOwnPropertyDescriptor;t.f=r?u:function(e,t){if(e=s(e),t=a(t,!0),c)try{return u(e,t)}catch(e){}if(l(e,t))return o(!i.f.call(e,t),e[t])}},6509:function(e,t,n){var r=n(5476),i=n(4789).f,o={}.toString,s="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return s&&"[object Window]"==o.call(e)?function(e){try{return i(e)}catch(e){return s.slice()}}(e):i(r(e))}},4789:function(e,t,n){var r=n(6347),i=n(290).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},8916:function(e,t){t.f=Object.getOwnPropertySymbols},6347:function(e,t,n){var r=n(1883),i=n(5476),o=n(8186).indexOf,s=n(7708);e.exports=function(e,t){var n,a=i(e),l=0,c=[];for(n in a)!r(s,n)&&r(a,n)&&c.push(n);for(;t.length>l;)r(a,n=t[l++])&&(~o(c,n)||c.push(n));return c}},1641:function(e,t,n){var r=n(6347),i=n(290);e.exports=Object.keys||function(e){return r(e,i)}},9304:function(e,t){"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,i=r&&!n.call({1:2},1);t.f=i?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},4972:function(e,t,n){"use strict";var r=n(3129),i=n(3062);e.exports=r?{}.toString:function(){return"[object "+i(this)+"]"}},5816:function(e,t,n){var r=n(6492),i=n(4789),o=n(8916),s=n(3938);e.exports=r("Reflect","ownKeys")||function(e){var t=i.f(s(e)),n=o.f;return n?t.concat(n(e)):t}},9720:function(e,t,n){var r=n(200);e.exports=r},7485:function(e,t,n){var r=n(200),i=n(7712),o=n(1883),s=n(5975),a=n(9965),l=n(9206),c=l.get,u=l.enforce,p=String(String).split("String");(e.exports=function(e,t,n,a){var l,c=!!a&&!!a.unsafe,d=!!a&&!!a.enumerable,f=!!a&&!!a.noTargetGet;"function"==typeof n&&("string"!=typeof t||o(n,"name")||i(n,"name",t),(l=u(n)).source||(l.source=p.join("string"==typeof t?t:""))),e!==r?(c?!f&&e[t]&&(d=!0):delete e[t],d?e[t]=n:i(e,t,n)):d?e[t]=n:s(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&c(this).source||a(this)}))},1229:function(e){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},5975:function(e,t,n){var r=n(200),i=n(7712);e.exports=function(e,t){try{i(r,e,t)}catch(n){r[e]=t}return t}},5282:function(e,t,n){var r=n(3610).f,i=n(1883),o=n(1602)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},5904:function(e,t,n){var r=n(2),i=n(665),o=r("keys");e.exports=function(e){return o[e]||(o[e]=i(e))}},9310:function(e,t,n){var r=n(200),i=n(5975),o="__core-js_shared__",s=r[o]||i(o,{});e.exports=s},2:function(e,t,n){var r=n(6926),i=n(9310);(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.14.0",mode:r?"pure":"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})},6539:function(e,t,n){var r=n(7317),i=Math.max,o=Math.min;e.exports=function(e,t){var n=r(e);return n<0?i(n+t,0):o(n,t)}},5476:function(e,t,n){var r=n(8664),i=n(1229);e.exports=function(e){return r(i(e))}},7317:function(e){var t=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:t)(e)}},3747:function(e,t,n){var r=n(7317),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},2612:function(e,t,n){var r=n(1229);e.exports=function(e){return Object(r(e))}},874:function(e,t,n){var r=n(5335);e.exports=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},3129:function(e,t,n){var r={};r[n(1602)("toStringTag")]="z",e.exports="[object z]"===String(r)},665:function(e){var t=0,n=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++t+n).toString(36)}},5225:function(e,t,n){var r=n(1849);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},802:function(e,t,n){var r=n(1602);t.f=r},1602:function(e,t,n){var r=n(200),i=n(2),o=n(1883),s=n(665),a=n(1849),l=n(5225),c=i("wks"),u=r.Symbol,p=l?u:u&&u.withoutSetter||s;e.exports=function(e){return o(c,e)&&(a||"string"==typeof c[e])||(a&&o(u,e)?c[e]=u[e]:c[e]=p("Symbol."+e)),c[e]}},115:function(e,t,n){"use strict";var r=n(1605),i=n(2074),o=n(8679),s=n(5335),a=n(2612),l=n(3747),c=n(2057),u=n(2998),p=n(5634),d=n(1602),f=n(6845),h=d("isConcatSpreadable"),m=9007199254740991,g="Maximum allowed index exceeded",y=f>=51||!i((function(){var e=[];return e[h]=!1,e.concat()[0]!==e})),b=p("concat"),v=function(e){if(!s(e))return!1;var t=e[h];return void 0!==t?!!t:o(e)};r({target:"Array",proto:!0,forced:!y||!b},{concat:function(e){var t,n,r,i,o,s=a(this),p=u(s,0),d=0;for(t=-1,r=arguments.length;tm)throw TypeError(g);for(n=0;n=m)throw TypeError(g);c(p,d++,o)}return p.length=d,p}})},1586:function(e,t,n){var r=n(200);n(5282)(r.JSON,"JSON",!0)},6982:function(e,t,n){n(5282)(Math,"Math",!0)},5086:function(e,t,n){var r=n(3129),i=n(7485),o=n(4972);r||i(Object.prototype,"toString",o,{unsafe:!0})},3719:function(e,t,n){var r=n(1605),i=n(200),o=n(5282);r({global:!0},{Reflect:{}}),o(i.Reflect,"Reflect",!0)},7727:function(e,t,n){n(1272)("asyncIterator")},590:function(e,t,n){"use strict";var r=n(1605),i=n(5077),o=n(200),s=n(1883),a=n(5335),l=n(3610).f,c=n(4361),u=o.Symbol;if(i&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var p={},d=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof d?new u(e):void 0===e?u():u(e);return""===e&&(p[t]=!0),t};c(d,u);var f=d.prototype=u.prototype;f.constructor=d;var h=f.toString,m="Symbol(test)"==String(u("test")),g=/^Symbol\((.*)\)[^)]+$/;l(f,"description",{configurable:!0,get:function(){var e=a(this)?this.valueOf():this,t=h.call(e);if(s(p,e))return"";var n=m?t.slice(7,-1):t.replace(g,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:d})}},8290:function(e,t,n){n(1272)("hasInstance")},2619:function(e,t,n){n(1272)("isConcatSpreadable")},4216:function(e,t,n){n(1272)("iterator")},3534:function(e,t,n){"use strict";var r=n(1605),i=n(200),o=n(6492),s=n(6926),a=n(5077),l=n(1849),c=n(5225),u=n(2074),p=n(1883),d=n(8679),f=n(5335),h=n(3938),m=n(2612),g=n(5476),y=n(874),b=n(6843),v=n(3105),x=n(1641),w=n(4789),k=n(6509),S=n(8916),E=n(7632),O=n(3610),_=n(9304),A=n(7712),C=n(7485),j=n(2),P=n(5904),T=n(7708),I=n(665),R=n(1602),N=n(802),$=n(1272),L=n(5282),D=n(9206),M=n(1344).forEach,F=P("hidden"),z="Symbol",B="prototype",U=R("toPrimitive"),q=D.set,V=D.getterFor(z),W=Object[B],H=i.Symbol,Y=o("JSON","stringify"),G=E.f,Q=O.f,X=k.f,K=_.f,Z=j("symbols"),J=j("op-symbols"),ee=j("string-to-symbol-registry"),te=j("symbol-to-string-registry"),ne=j("wks"),re=i.QObject,ie=!re||!re[B]||!re[B].findChild,oe=a&&u((function(){return 7!=v(Q({},"a",{get:function(){return Q(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=G(W,t);r&&delete W[t],Q(e,t,n),r&&e!==W&&Q(W,t,r)}:Q,se=function(e,t){var n=Z[e]=v(H[B]);return q(n,{type:z,tag:e,description:t}),a||(n.description=t),n},ae=c?function(e){return"symbol"==typeof e}:function(e){return Object(e)instanceof H},le=function(e,t,n){e===W&&le(J,t,n),h(e);var r=y(t,!0);return h(n),p(Z,r)?(n.enumerable?(p(e,F)&&e[F][r]&&(e[F][r]=!1),n=v(n,{enumerable:b(0,!1)})):(p(e,F)||Q(e,F,b(1,{})),e[F][r]=!0),oe(e,r,n)):Q(e,r,n)},ce=function(e,t){h(e);var n=g(t),r=x(n).concat(fe(n));return M(r,(function(t){a&&!ue.call(n,t)||le(e,t,n[t])})),e},ue=function(e){var t=y(e,!0),n=K.call(this,t);return!(this===W&&p(Z,t)&&!p(J,t))&&(!(n||!p(this,t)||!p(Z,t)||p(this,F)&&this[F][t])||n)},pe=function(e,t){var n=g(e),r=y(t,!0);if(n!==W||!p(Z,r)||p(J,r)){var i=G(n,r);return!i||!p(Z,r)||p(n,F)&&n[F][r]||(i.enumerable=!0),i}},de=function(e){var t=X(g(e)),n=[];return M(t,(function(e){p(Z,e)||p(T,e)||n.push(e)})),n},fe=function(e){var t=e===W,n=X(t?J:g(e)),r=[];return M(n,(function(e){!p(Z,e)||t&&!p(W,e)||r.push(Z[e])})),r};l||(H=function(){if(this instanceof H)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,t=I(e),n=function(e){this===W&&n.call(J,e),p(this,F)&&p(this[F],t)&&(this[F][t]=!1),oe(this,t,b(1,e))};return a&&ie&&oe(W,t,{configurable:!0,set:n}),se(t,e)},C(H[B],"toString",(function(){return V(this).tag})),C(H,"withoutSetter",(function(e){return se(I(e),e)})),_.f=ue,O.f=le,E.f=pe,w.f=k.f=de,S.f=fe,N.f=function(e){return se(R(e),e)},a&&(Q(H[B],"description",{configurable:!0,get:function(){return V(this).description}}),s||C(W,"propertyIsEnumerable",ue,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!l,sham:!l},{Symbol:H}),M(x(ne),(function(e){$(e)})),r({target:z,stat:!0,forced:!l},{for:function(e){var t=String(e);if(p(ee,t))return ee[t];var n=H(t);return ee[t]=n,te[n]=t,n},keyFor:function(e){if(!ae(e))throw TypeError(e+" is not a symbol");if(p(te,e))return te[e]},useSetter:function(){ie=!0},useSimple:function(){ie=!1}}),r({target:"Object",stat:!0,forced:!l,sham:!a},{create:function(e,t){return void 0===t?v(e):ce(v(e),t)},defineProperty:le,defineProperties:ce,getOwnPropertyDescriptor:pe}),r({target:"Object",stat:!0,forced:!l},{getOwnPropertyNames:de,getOwnPropertySymbols:fe}),r({target:"Object",stat:!0,forced:u((function(){S.f(1)}))},{getOwnPropertySymbols:function(e){return S.f(m(e))}}),Y&&r({target:"JSON",stat:!0,forced:!l||u((function(){var e=H();return"[null]"!=Y([e])||"{}"!=Y({a:e})||"{}"!=Y(Object(e))}))},{stringify:function(e,t,n){for(var r,i=[e],o=1;arguments.length>o;)i.push(arguments[o++]);if(r=t,(f(t)||void 0!==e)&&!ae(e))return d(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!ae(t))return t}),i[1]=t,Y.apply(null,i)}}),H[B][U]||A(H[B],U,H[B].valueOf),L(H,z),T[F]=!0},6195:function(e,t,n){n(1272)("matchAll")},2957:function(e,t,n){n(1272)("match")},4100:function(e,t,n){n(1272)("replace")},3006:function(e,t,n){n(1272)("search")},4910:function(e,t,n){n(1272)("species")},2820:function(e,t,n){n(1272)("split")},6611:function(e,t,n){n(1272)("toPrimitive")},9576:function(e,t,n){n(1272)("toStringTag")},9747:function(e,t,n){n(1272)("unscopables")},8997:function(e,t,n){"use strict";var r=n(4991),i=n.n(r),o=n(6314),s=n.n(o)()(i());s.push([e.id,".ps{overflow:hidden!important;overflow-anchor:none;-ms-overflow-style:none;touch-action:auto;-ms-touch-action:auto}.ps__rail-x{display:none;opacity:0;transition:background-color .2s linear,opacity .2s linear;-webkit-transition:background-color .2s linear,opacity .2s linear;height:15px;bottom:0;position:absolute}.ps__rail-y{display:none;opacity:0;transition:background-color .2s linear,opacity .2s linear;-webkit-transition:background-color .2s linear,opacity .2s linear;width:15px;right:0;position:absolute}.ps--active-x>.ps__rail-x,.ps--active-y>.ps__rail-y{display:block;background-color:transparent}.ps:hover>.ps__rail-x,.ps:hover>.ps__rail-y,.ps--focus>.ps__rail-x,.ps--focus>.ps__rail-y,.ps--scrolling-x>.ps__rail-x,.ps--scrolling-y>.ps__rail-y{opacity:.6}.ps .ps__rail-x:hover,.ps .ps__rail-y:hover,.ps .ps__rail-x:focus,.ps .ps__rail-y:focus,.ps .ps__rail-x.ps--clicking,.ps .ps__rail-y.ps--clicking{background-color:#eee;opacity:.9}.ps__thumb-x{background-color:#aaa;border-radius:6px;transition:background-color .2s linear,height .2s ease-in-out;-webkit-transition:background-color .2s linear,height .2s ease-in-out;height:6px;bottom:2px;position:absolute}.ps__thumb-y{background-color:#aaa;border-radius:6px;transition:background-color .2s linear,width .2s ease-in-out;-webkit-transition:background-color .2s linear,width .2s ease-in-out;width:6px;right:2px;position:absolute}.ps__rail-x:hover>.ps__thumb-x,.ps__rail-x:focus>.ps__thumb-x,.ps__rail-x.ps--clicking .ps__thumb-x{background-color:#999;height:11px}.ps__rail-y:hover>.ps__thumb-y,.ps__rail-y:focus>.ps__thumb-y,.ps__rail-y.ps--clicking .ps__thumb-y{background-color:#999;width:11px}@supports (-ms-overflow-style: none){.ps{overflow:auto!important}}@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ps{overflow:auto!important}}\n","",{version:3,sources:["webpack://./node_modules/perfect-scrollbar/css/perfect-scrollbar.css"],names:[],mappings:"AAGA,IACE,yBAAU,CACV,oBAAiB,CACjB,uBAAoB,CACpB,iBAAc,CACd,qBACF,CAKA,YACE,YAAS,CACT,SAAS,CACT,yDAAqD,CACrD,iEAA6D,CAC7D,WAAQ,CAER,QAAQ,CAER,iBACF,CAEA,YACE,YAAS,CACT,SAAS,CACT,yDAAqD,CACrD,iEAA6D,CAC7D,UAAO,CAEP,OAAO,CAEP,iBACF,CAEA,oDAEE,aAAS,CACT,4BACF,CAEA,oJAME,UACF,CAEA,kJAME,qBAAkB,CAClB,UACF,CAKA,aACE,qBAAkB,CAnEpB,iBAoEiB,CACf,6DAAoD,CACpD,qEAA4D,CAC5D,UAAQ,CAER,UAAQ,CAER,iBACF,CAEA,aACE,qBAAkB,CA/EpB,iBAgFiB,CACf,4DAAmD,CACnD,oEAA2D,CAC3D,SAAO,CAEP,SAAO,CAEP,iBACF,CAEA,oGAGE,qBAAkB,CAClB,WACF,CAEA,oGAGE,qBAAkB,CAClB,UACF,CAGA,qCACE,IACE,uBACF,CACF,CAEA,wEACE,IACE,uBACF,CACF",sourcesContent:["/*\n * Container style\n */\n.ps {\n overflow: hidden !important;\n overflow-anchor: none;\n -ms-overflow-style: none;\n touch-action: auto;\n -ms-touch-action: auto;\n}\n\n/*\n * Scrollbar rail styles\n */\n.ps__rail-x {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n height: 15px;\n /* there must be 'bottom' or 'top' for ps__rail-x */\n bottom: 0px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-y {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n width: 15px;\n /* there must be 'right' or 'left' for ps__rail-y */\n right: 0;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps--active-x > .ps__rail-x,\n.ps--active-y > .ps__rail-y {\n display: block;\n background-color: transparent;\n}\n\n.ps:hover > .ps__rail-x,\n.ps:hover > .ps__rail-y,\n.ps--focus > .ps__rail-x,\n.ps--focus > .ps__rail-y,\n.ps--scrolling-x > .ps__rail-x,\n.ps--scrolling-y > .ps__rail-y {\n opacity: 0.6;\n}\n\n.ps .ps__rail-x:hover,\n.ps .ps__rail-y:hover,\n.ps .ps__rail-x:focus,\n.ps .ps__rail-y:focus,\n.ps .ps__rail-x.ps--clicking,\n.ps .ps__rail-y.ps--clicking {\n background-color: #eee;\n opacity: 0.9;\n}\n\n/*\n * Scrollbar thumb styles\n */\n.ps__thumb-x {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, height .2s ease-in-out;\n -webkit-transition: background-color .2s linear, height .2s ease-in-out;\n height: 6px;\n /* there must be 'bottom' for ps__thumb-x */\n bottom: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__thumb-y {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, width .2s ease-in-out;\n -webkit-transition: background-color .2s linear, width .2s ease-in-out;\n width: 6px;\n /* there must be 'right' for ps__thumb-y */\n right: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-x:hover > .ps__thumb-x,\n.ps__rail-x:focus > .ps__thumb-x,\n.ps__rail-x.ps--clicking .ps__thumb-x {\n background-color: #999;\n height: 11px;\n}\n\n.ps__rail-y:hover > .ps__thumb-y,\n.ps__rail-y:focus > .ps__thumb-y,\n.ps__rail-y.ps--clicking .ps__thumb-y {\n background-color: #999;\n width: 11px;\n}\n\n/* MS supports */\n@supports (-ms-overflow-style: none) {\n .ps {\n overflow: auto !important;\n }\n}\n\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n .ps {\n overflow: auto !important;\n }\n}\n"],sourceRoot:""}]),t.A=s},6314:function(e){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var i={};if(r)for(var o=0;oe.length)&&(t=e.length);for(var n=0,r=new Array(t);n2?r:e).apply(void 0,i)}}e.memoize=s,e.debounce=a,e.bind=l,e.default={memoize:s,debounce:a,bind:l}},void 0===(r=n.apply(t,[t]))||(e.exports=r)},6364:function(e){e.exports={}},228:function(e){"use strict";var t=Object.prototype.hasOwnProperty,n="~";function r(){}function i(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function o(e,t,r,o,s){if("function"!=typeof r)throw new TypeError("The listener must be a function");var a=new i(r,o||e,s),l=n?n+t:t;return e._events[l]?e._events[l].fn?e._events[l]=[e._events[l],a]:e._events[l].push(a):(e._events[l]=a,e._eventsCount++),e}function s(e,t){0==--e._eventsCount?e._events=new r:delete e._events[t]}function a(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=Object.create(null),(new r).__proto__||(n=!1)),a.prototype.eventNames=function(){var e,r,i=[];if(0===this._eventsCount)return i;for(r in e=this._events)t.call(e,r)&&i.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},a.prototype.listeners=function(e){var t=n?n+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var i=0,o=r.length,s=new Array(o);iu.depthLimit)return void a(t,e,r,s);if(void 0!==u.edgesLimit&&i+1>u.edgesLimit)return void a(t,e,r,s);if(o.push(e),Array.isArray(e))for(p=0;pt?1:0}function u(e,t,n,s){void 0===s&&(s=o());var a,l=p(e,"",0,[],void 0,0,s)||e;try{a=0===i.length?JSON.stringify(l,t,n):JSON.stringify(l,d(t),n)}catch(e){return JSON.stringify("[unable to serialize, circular reference is too complex to analyze]")}finally{for(;0!==r.length;){var c=r.pop();4===c.length?Object.defineProperty(c[0],c[1],c[3]):c[0][c[1]]=c[2]}}return a}function p(e,i,o,s,l,u,d){var f;if(u+=1,"object"==typeof e&&null!==e){for(f=0;fd.depthLimit)return void a(t,e,i,l);if(void 0!==d.edgesLimit&&o+1>d.edgesLimit)return void a(t,e,i,l);if(s.push(e),Array.isArray(e))for(f=0;f0)for(var r=0;r{for(const n of e){if("string"==typeof n&&t===n)return!0;if(n instanceof RegExp&&n.test(t))return!0}}:()=>!1}},5334:function(e,t){"use strict";const n=":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",r="["+n+"]["+n+"\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*",i=new RegExp("^"+r+"$");t.isExist=function(e){return void 0!==e},t.isEmptyObject=function(e){return 0===Object.keys(e).length},t.merge=function(e,t,n){if(t){const r=Object.keys(t),i=r.length;for(let o=0;o5&&"xml"===r)return h("InvalidXml","XML declaration allowed only at the start of the document.",g(e,t));if("?"==e[t]&&">"==e[t+1]){t++;break}}return t}function a(e,t){if(e.length>t+5&&"-"===e[t+1]&&"-"===e[t+2]){for(t+=3;t"===e[t+2]){t+=2;break}}else if(e.length>t+8&&"D"===e[t+1]&&"O"===e[t+2]&&"C"===e[t+3]&&"T"===e[t+4]&&"Y"===e[t+5]&&"P"===e[t+6]&&"E"===e[t+7]){let n=1;for(t+=8;t"===e[t]&&(n--,0===n))break}else if(e.length>t+9&&"["===e[t+1]&&"C"===e[t+2]&&"D"===e[t+3]&&"A"===e[t+4]&&"T"===e[t+5]&&"A"===e[t+6]&&"["===e[t+7])for(t+=8;t"===e[t+2]){t+=2;break}return t}t.validate=function(e,t){t=Object.assign({},i,t);const n=[];let l=!1,c=!1;"\ufeff"===e[0]&&(e=e.substr(1));for(let i=0;i"!==e[i]&&" "!==e[i]&&"\t"!==e[i]&&"\n"!==e[i]&&"\r"!==e[i];i++)b+=e[i];if(b=b.trim(),"/"===b[b.length-1]&&(b=b.substring(0,b.length-1),i--),p=b,!r.isName(p)){let t;return t=0===b.trim().length?"Invalid space after '<'.":"Tag '"+b+"' is an invalid name.",h("InvalidTag",t,g(e,i))}const v=u(e,i);if(!1===v)return h("InvalidAttr","Attributes for '"+b+"' have open quote.",g(e,i));let x=v.value;if(i=v.index,"/"===x[x.length-1]){const n=i-x.length;x=x.substring(0,x.length-1);const r=d(x,t);if(!0!==r)return h(r.err.code,r.err.msg,g(e,n+r.err.line));l=!0}else if(y){if(!v.tagClosed)return h("InvalidTag","Closing tag '"+b+"' doesn't have proper closing.",g(e,i));if(x.trim().length>0)return h("InvalidTag","Closing tag '"+b+"' can't have attributes or invalid starting.",g(e,m));if(0===n.length)return h("InvalidTag","Closing tag '"+b+"' has not been opened.",g(e,m));{const t=n.pop();if(b!==t.tagName){let n=g(e,t.tagStartPos);return h("InvalidTag","Expected closing tag '"+t.tagName+"' (opened in line "+n.line+", col "+n.col+") instead of closing tag '"+b+"'.",g(e,m))}0==n.length&&(c=!0)}}else{const r=d(x,t);if(!0!==r)return h(r.err.code,r.err.msg,g(e,i-x.length+r.err.line));if(!0===c)return h("InvalidXml","Multiple possible root nodes found.",g(e,i));-1!==t.unpairedTags.indexOf(b)||n.push({tagName:b,tagStartPos:m}),l=!0}for(i++;i0)||h("InvalidXml","Invalid '"+JSON.stringify(n.map((e=>e.tagName)),null,4).replace(/\r?\n/g,"")+"' found.",{line:1,col:1}):h("InvalidXml","Start tag expected.",1)};const l='"',c="'";function u(e,t){let n="",r="",i=!1;for(;t"===e[t]&&""===r){i=!0;break}n+=e[t]}return""===r&&{value:n,index:t,tagClosed:i}}const p=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function d(e,t){const n=r.getAllMatches(e,p),i={};for(let e=0;e","g"),val:">"},{regex:new RegExp("<","g"),val:"<"},{regex:new RegExp("'","g"),val:"'"},{regex:new RegExp('"',"g"),val:"""}],processEntities:!0,stopNodes:[],oneListGroup:!1};function s(e){this.options=Object.assign({},o,e),!0===this.options.ignoreAttributes||this.options.attributesGroupName?this.isAttribute=function(){return!1}:(this.ignoreAttributesFn=i(this.options.ignoreAttributes),this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=c),this.processTextOrObjNode=a,this.options.format?(this.indentate=l,this.tagEndChar=">\n",this.newLine="\n"):(this.indentate=function(){return""},this.tagEndChar=">",this.newLine="")}function a(e,t,n,r){const i=this.j2x(e,n+1,r.concat(t));return void 0!==e[this.options.textNodeName]&&1===Object.keys(e).length?this.buildTextValNode(e[this.options.textNodeName],t,i.attrStr,n):this.buildObjectNode(i.val,t,i.attrStr,n)}function l(e){return this.options.indentBy.repeat(e)}function c(e){return!(!e.startsWith(this.options.attributeNamePrefix)||e===this.options.textNodeName)&&e.substr(this.attrPrefixLen)}s.prototype.build=function(e){return this.options.preserveOrder?r(e,this.options):(Array.isArray(e)&&this.options.arrayNodeName&&this.options.arrayNodeName.length>1&&(e={[this.options.arrayNodeName]:e}),this.j2x(e,0,[]).val)},s.prototype.j2x=function(e,t,n){let r="",i="";const o=n.join(".");for(let s in e)if(Object.prototype.hasOwnProperty.call(e,s))if(void 0===e[s])this.isAttribute(s)&&(i+="");else if(null===e[s])this.isAttribute(s)||s===this.options.cdataPropName?i+="":"?"===s[0]?i+=this.indentate(t)+"<"+s+"?"+this.tagEndChar:i+=this.indentate(t)+"<"+s+"/"+this.tagEndChar;else if(e[s]instanceof Date)i+=this.buildTextValNode(e[s],s,"",t);else if("object"!=typeof e[s]){const n=this.isAttribute(s);if(n&&!this.ignoreAttributesFn(n,o))r+=this.buildAttrPairStr(n,""+e[s]);else if(!n)if(s===this.options.textNodeName){let t=this.options.tagValueProcessor(s,""+e[s]);i+=this.replaceEntitiesValue(t)}else i+=this.buildTextValNode(e[s],s,"",t)}else if(Array.isArray(e[s])){const r=e[s].length;let o="",a="";for(let l=0;l"+e+i}},s.prototype.closeTag=function(e){let t="";return-1!==this.options.unpairedTags.indexOf(e)?this.options.suppressUnpairedNode||(t="/"):t=this.options.suppressEmptyNode?"/":`>`+this.newLine;if(!1!==this.options.commentPropName&&t===this.options.commentPropName)return this.indentate(r)+`\x3c!--${e}--\x3e`+this.newLine;if("?"===t[0])return this.indentate(r)+"<"+t+n+"?"+this.tagEndChar;{let i=this.options.tagValueProcessor(t,e);return i=this.replaceEntitiesValue(i),""===i?this.indentate(r)+"<"+t+n+this.closeTag(t)+this.tagEndChar:this.indentate(r)+"<"+t+n+">"+i+"0&&this.options.processEntities)for(let t=0;t`,u=!1;continue}if(f===s.commentPropName){c+=l+`\x3c!--${d[f][0][s.textNodeName]}--\x3e`,u=!0;continue}if("?"===f[0]){const e=r(d[":@"],s),t="?xml"===f?"":l;let n=d[f][0][s.textNodeName];n=0!==n.length?" "+n:"",c+=t+`<${f}${n}${e}?>`,u=!0;continue}let m=l;""!==m&&(m+=s.indentBy);const g=l+`<${f}${r(d[":@"],s)}`,y=t(d[f],s,h,m);-1!==s.unpairedTags.indexOf(f)?s.suppressUnpairedNode?c+=g+">":c+=g+"/>":y&&0!==y.length||!s.suppressEmptyNode?y&&y.endsWith(">")?c+=g+`>${y}${l}`:(c+=g+">",y&&""!==l&&(y.includes("/>")||y.includes("`):c+=g+"/>",u=!0}return c}function n(e){const t=Object.keys(e);for(let n=0;n0&&t.processEntities)for(let n=0;n0&&(r="\n"),t(e,n,"",r)}},9400:function(e,t,n){const r=n(5334);function i(e,t){let n="";for(;t"===e[t]){if(d?"-"===e[t-1]&&"-"===e[t-2]&&(d=!1,r--):r--,0===r)break}else"["===e[t]?p=!0:f+=e[t];else{if(p&&s(e,t)){let r,o;t+=7,[r,o,t]=i(e,t+1),-1===o.indexOf("&")&&(n[u(r)]={regx:RegExp(`&${r};`,"g"),val:o})}else if(p&&a(e,t))t+=8;else if(p&&l(e,t))t+=8;else if(p&&c(e,t))t+=9;else{if(!o)throw new Error("Invalid DOCTYPE");d=!0}r++,f=""}if(0!==r)throw new Error("Unclosed DOCTYPE")}return{entities:n,i:t}}},460:function(e,t){const n={preserveOrder:!1,attributeNamePrefix:"@_",attributesGroupName:!1,textNodeName:"#text",ignoreAttributes:!0,removeNSPrefix:!1,allowBooleanAttributes:!1,parseTagValue:!0,parseAttributeValue:!1,trimValues:!0,cdataPropName:!1,numberParseOptions:{hex:!0,leadingZeros:!0,eNotation:!0},tagValueProcessor:function(e,t){return t},attributeValueProcessor:function(e,t){return t},stopNodes:[],alwaysCreateTextNode:!1,isArray:()=>!1,commentPropName:!1,unpairedTags:[],processEntities:!0,htmlEntities:!1,ignoreDeclaration:!1,ignorePiTags:!1,transformTagName:!1,transformAttributeName:!1,updateTag:function(e,t,n){return e}};t.buildOptions=function(e){return Object.assign({},n,e)},t.defaultOptions=n},7680:function(e,t,n){"use strict";const r=n(5334),i=n(3832),o=n(9400),s=n(7983),a=n(3085);function l(e){const t=Object.keys(e);for(let n=0;n0)){s||(e=this.replaceEntitiesValue(e));const r=this.options.tagValueProcessor(t,e,n,i,o);return null==r?e:typeof r!=typeof e||r!==e?r:this.options.trimValues||e.trim()===e?w(e,this.options.parseTagValue,this.options.numberParseOptions):e}}function u(e){if(this.options.removeNSPrefix){const t=e.split(":"),n="/"===e.charAt(0)?"/":"";if("xmlns"===t[0])return"";2===t.length&&(e=n+t[1])}return e}const p=new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])([\\s\\S]*?)\\3)?","gm");function d(e,t,n){if(!0!==this.options.ignoreAttributes&&"string"==typeof e){const n=r.getAllMatches(e,p),i=n.length,o={};for(let e=0;e",a,"Closing Tag is not closed.");let i=e.substring(a+2,t).trim();if(this.options.removeNSPrefix){const e=i.indexOf(":");-1!==e&&(i=i.substr(e+1))}this.options.transformTagName&&(i=this.options.transformTagName(i)),n&&(r=this.saveTextToParentTag(r,n,s));const o=s.substring(s.lastIndexOf(".")+1);if(i&&-1!==this.options.unpairedTags.indexOf(i))throw new Error(`Unpaired tag can not be used as closing tag: `);let l=0;o&&-1!==this.options.unpairedTags.indexOf(o)?(l=s.lastIndexOf(".",s.lastIndexOf(".")-1),this.tagsNodeStack.pop()):l=s.lastIndexOf("."),s=s.substring(0,l),n=this.tagsNodeStack.pop(),r="",a=t}else if("?"===e[a+1]){let t=v(e,a,!1,"?>");if(!t)throw new Error("Pi Tag is not closed.");if(r=this.saveTextToParentTag(r,n,s),this.options.ignoreDeclaration&&"?xml"===t.tagName||this.options.ignorePiTags);else{const e=new i(t.tagName);e.add(this.options.textNodeName,""),t.tagName!==t.tagExp&&t.attrExpPresent&&(e[":@"]=this.buildAttributesMap(t.tagExp,s,t.tagName)),this.addChild(n,e,s)}a=t.closeIndex+1}else if("!--"===e.substr(a+1,3)){const t=b(e,"--\x3e",a+4,"Comment is not closed.");if(this.options.commentPropName){const i=e.substring(a+4,t-2);r=this.saveTextToParentTag(r,n,s),n.add(this.options.commentPropName,[{[this.options.textNodeName]:i}])}a=t}else if("!D"===e.substr(a+1,2)){const t=o(e,a);this.docTypeEntities=t.entities,a=t.i}else if("!["===e.substr(a+1,2)){const t=b(e,"]]>",a,"CDATA is not closed.")-2,i=e.substring(a+9,t);r=this.saveTextToParentTag(r,n,s);let o=this.parseTextData(i,n.tagname,s,!0,!1,!0,!0);null==o&&(o=""),this.options.cdataPropName?n.add(this.options.cdataPropName,[{[this.options.textNodeName]:i}]):n.add(this.options.textNodeName,o),a=t+2}else{let o=v(e,a,this.options.removeNSPrefix),l=o.tagName;const c=o.rawTagName;let u=o.tagExp,p=o.attrExpPresent,d=o.closeIndex;this.options.transformTagName&&(l=this.options.transformTagName(l)),n&&r&&"!xml"!==n.tagname&&(r=this.saveTextToParentTag(r,n,s,!1));const f=n;if(f&&-1!==this.options.unpairedTags.indexOf(f.tagname)&&(n=this.tagsNodeStack.pop(),s=s.substring(0,s.lastIndexOf("."))),l!==t.tagname&&(s+=s?"."+l:l),this.isItStopNode(this.options.stopNodes,s,l)){let t="";if(u.length>0&&u.lastIndexOf("/")===u.length-1)"/"===l[l.length-1]?(l=l.substr(0,l.length-1),s=s.substr(0,s.length-1),u=l):u=u.substr(0,u.length-1),a=o.closeIndex;else if(-1!==this.options.unpairedTags.indexOf(l))a=o.closeIndex;else{const n=this.readStopNodeData(e,c,d+1);if(!n)throw new Error(`Unexpected end of ${c}`);a=n.i,t=n.tagContent}const r=new i(l);l!==u&&p&&(r[":@"]=this.buildAttributesMap(u,s,l)),t&&(t=this.parseTextData(t,l,s,!0,p,!0,!0)),s=s.substr(0,s.lastIndexOf(".")),r.add(this.options.textNodeName,t),this.addChild(n,r,s)}else{if(u.length>0&&u.lastIndexOf("/")===u.length-1){"/"===l[l.length-1]?(l=l.substr(0,l.length-1),s=s.substr(0,s.length-1),u=l):u=u.substr(0,u.length-1),this.options.transformTagName&&(l=this.options.transformTagName(l));const e=new i(l);l!==u&&p&&(e[":@"]=this.buildAttributesMap(u,s,l)),this.addChild(n,e,s),s=s.substr(0,s.lastIndexOf("."))}else{const e=new i(l);this.tagsNodeStack.push(n),l!==u&&p&&(e[":@"]=this.buildAttributesMap(u,s,l)),this.addChild(n,e,s),n=e}r="",a=d}}else r+=e[a];return t.child};function h(e,t,n){const r=this.options.updateTag(t.tagname,n,t[":@"]);!1===r||("string"==typeof r?(t.tagname=r,e.addChild(t)):e.addChild(t))}const m=function(e){if(this.options.processEntities){for(let t in this.docTypeEntities){const n=this.docTypeEntities[t];e=e.replace(n.regx,n.val)}for(let t in this.lastEntities){const n=this.lastEntities[t];e=e.replace(n.regex,n.val)}if(this.options.htmlEntities)for(let t in this.htmlEntities){const n=this.htmlEntities[t];e=e.replace(n.regex,n.val)}e=e.replace(this.ampEntity.regex,this.ampEntity.val)}return e};function g(e,t,n,r){return e&&(void 0===r&&(r=0===t.child.length),void 0!==(e=this.parseTextData(e,t.tagname,n,!1,!!t[":@"]&&0!==Object.keys(t[":@"]).length,r))&&""!==e&&t.add(this.options.textNodeName,e),e=""),e}function y(e,t,n){const r="*."+n;for(const n in e){const i=e[n];if(r===i||t===i)return!0}return!1}function b(e,t,n,r){const i=e.indexOf(t,n);if(-1===i)throw new Error(r);return i+t.length-1}function v(e,t,n,r=">"){const i=function(e,t,n=">"){let r,i="";for(let o=t;o",n,`${t} is not closed`);if(e.substring(n+2,o).trim()===t&&(i--,0===i))return{tagContent:e.substring(r,n),i:o};n=o}else if("?"===e[n+1])n=b(e,"?>",n+1,"StopNode is not closed.");else if("!--"===e.substr(n+1,3))n=b(e,"--\x3e",n+3,"StopNode is not closed.");else if("!["===e.substr(n+1,2))n=b(e,"]]>",n,"StopNode is not closed.")-2;else{const r=v(e,n,">");r&&((r&&r.tagName)===t&&"/"!==r.tagExp[r.tagExp.length-1]&&i++,n=r.closeIndex)}}function w(e,t,n){if(t&&"string"==typeof e){const t=e.trim();return"true"===t||"false"!==t&&s(e,n)}return r.isExist(e)?e:""}e.exports=class{constructor(e){this.options=e,this.currentNode=null,this.tagsNodeStack=[],this.docTypeEntities={},this.lastEntities={apos:{regex:/&(apos|#39|#x27);/g,val:"'"},gt:{regex:/&(gt|#62|#x3E);/g,val:">"},lt:{regex:/&(lt|#60|#x3C);/g,val:"<"},quot:{regex:/&(quot|#34|#x22);/g,val:'"'}},this.ampEntity={regex:/&(amp|#38|#x26);/g,val:"&"},this.htmlEntities={space:{regex:/&(nbsp|#160);/g,val:" "},cent:{regex:/&(cent|#162);/g,val:"¢"},pound:{regex:/&(pound|#163);/g,val:"£"},yen:{regex:/&(yen|#165);/g,val:"¥"},euro:{regex:/&(euro|#8364);/g,val:"€"},copyright:{regex:/&(copy|#169);/g,val:"©"},reg:{regex:/&(reg|#174);/g,val:"®"},inr:{regex:/&(inr|#8377);/g,val:"₹"},num_dec:{regex:/&#([0-9]{1,7});/g,val:(e,t)=>String.fromCharCode(Number.parseInt(t,10))},num_hex:{regex:/&#x([0-9a-fA-F]{1,6});/g,val:(e,t)=>String.fromCharCode(Number.parseInt(t,16))}},this.addExternalEntities=l,this.parseXml=f,this.parseTextData=c,this.resolveNameSpace=u,this.buildAttributesMap=d,this.isItStopNode=y,this.replaceEntitiesValue=m,this.readStopNodeData=x,this.saveTextToParentTag=g,this.addChild=h,this.ignoreAttributesFn=a(this.options.ignoreAttributes)}}},2923:function(e,t,n){const{buildOptions:r}=n(460),i=n(7680),{prettify:o}=n(5629),s=n(3918);e.exports=class{constructor(e){this.externalEntities={},this.options=r(e)}parse(e,t){if("string"==typeof e);else{if(!e.toString)throw new Error("XML data is accepted in String or Bytes[] form.");e=e.toString()}if(t){!0===t&&(t={});const n=s.validate(e,t);if(!0!==n)throw Error(`${n.err.msg}:${n.err.line}:${n.err.col}`)}const n=new i(this.options);n.addExternalEntities(this.externalEntities);const r=n.parseXml(e);return this.options.preserveOrder||void 0===r?r:o(r,this.options)}addEntity(e,t){if(-1!==t.indexOf("&"))throw new Error("Entity value can't have '&'");if(-1!==e.indexOf("&")||-1!==e.indexOf(";"))throw new Error("An entity must be set without '&' and ';'. Eg. use '#xD' for ' '");if("&"===t)throw new Error("An entity with value '&' is not permitted");this.externalEntities[e]=t}}},5629:function(e,t){"use strict";function n(e,t,s){let a;const l={};for(let c=0;c0&&(l[t.textNodeName]=a):void 0!==a&&(l[t.textNodeName]=a),l}function r(e){const t=Object.keys(e);for(let e=0;e0?this.child.push({[e.tagname]:e.child,":@":e[":@"]}):this.child.push({[e.tagname]:e.child})}}},7593:function(e){var t=Object.prototype.hasOwnProperty,n=Object.prototype.toString;e.exports=function(e,r,i){if("[object Function]"!==n.call(r))throw new TypeError("iterator must be a function");var o=e.length;if(o===+o)for(var s=0;s=55296&&r<=56319&&t+1=56320&&n<=57343?1024*(r-55296)+n-56320+65536:r}function H(e){return/^\n* /.test(e)}var Y=1,G=2,Q=3,X=4,K=5;function Z(e,t,n,r,o){e.dump=function(){if(0===t.length)return e.quotingType===D?'""':"''";if(!e.noCompatMode&&(-1!==N.indexOf(t)||$.test(t)))return e.quotingType===D?'"'+t+'"':"'"+t+"'";var s=e.indent*Math.max(1,n),a=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-s),c=r||e.flowLevel>-1&&n>=e.flowLevel;switch(function(e,t,n,r,i,o,s,a){var c,p,d=0,R=null,N=!1,$=!1,L=-1!==r,M=-1,F=U(p=W(e,0))&&p!==l&&!B(p)&&p!==w&&p!==O&&p!==k&&p!==x&&p!==A&&p!==C&&p!==P&&p!==I&&p!==m&&p!==y&&p!==v&&p!==f&&p!==T&&p!==S&&p!==E&&p!==b&&p!==h&&p!==g&&p!==_&&p!==j&&function(e){return!B(e)&&e!==k}(W(e,e.length-1));if(t||s)for(c=0;c=65536?c+=2:c++){if(!U(d=W(e,c)))return K;F=F&&V(d,R,a),R=d}else{for(c=0;c=65536?c+=2:c++){if((d=W(e,c))===u)N=!0,L&&($=$||c-M-1>r&&" "!==e[M+1],M=c);else if(!U(d))return K;F=F&&V(d,R,a),R=d}$=$||L&&c-M-1>r&&" "!==e[M+1]}return N||$?n>9&&H(e)?K:s?o===D?K:G:$?X:Q:!F||s||i(e)?o===D?K:G:Y}(t,c,e.indent,a,(function(t){return function(e,t){var n,r;for(n=0,r=e.implicitTypes.length;n"+J(t,e.indent)+ee(F(function(e,t){for(var n,r,i,o=/(\n+)([^\n]*)/g,s=(i=-1!==(i=e.indexOf("\n"))?i:e.length,o.lastIndex=i,te(e.slice(0,i),t)),a="\n"===e[0]||" "===e[0];r=o.exec(e);){var l=r[1],c=r[2];n=" "===c[0],s+=l+(a||n||""===c?"":"\n")+te(c,t),a=n}return s}(t,a),s));case K:return'"'+function(e){for(var t,n="",r=0,i=0;i=65536?i+=2:i++)r=W(e,i),!(t=R[r])&&U(r)?(n+=e[i],r>=65536&&(n+=e[i+1])):n+=t||L(r);return n}(t)+'"';default:throw new i("impossible error: invalid scalar style")}}()}function J(e,t){var n=H(e)?String(t):"",r="\n"===e[e.length-1];return n+(!r||"\n"!==e[e.length-2]&&"\n"!==e?r?"":"-":"+")+"\n"}function ee(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function te(e,t){if(""===e||" "===e[0])return e;for(var n,r,i=/ [^ ]/g,o=0,s=0,a=0,l="";n=i.exec(e);)(a=n.index)-o>t&&(r=s>o?s:a,l+="\n"+e.slice(o,r),o=r+1),s=a;return l+="\n",e.length-o>t&&s>o?l+=e.slice(o,s)+"\n"+e.slice(s+1):l+=e.slice(o),l.slice(1)}function ne(e,t,n,r){var i,o,s,a="",l=e.tag;for(i=0,o=n.length;i tag resolver accepts not "'+p+'" style');r=u.represent[p](t,p)}e.dump=r}return!0}return!1}function ie(e,t,n,r,o,a,l){e.tag=null,e.dump=n,re(e,n,!1)||re(e,n,!0);var c,p=s.call(e.dump),d=r;r&&(r=e.flowLevel<0||e.flowLevel>t);var f,h,m="[object Object]"===p||"[object Array]"===p;if(m&&(h=-1!==(f=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||h||2!==e.indent&&t>0)&&(o=!1),h&&e.usedDuplicates[f])e.dump="*ref_"+f;else{if(m&&h&&!e.usedDuplicates[f]&&(e.usedDuplicates[f]=!0),"[object Object]"===p)r&&0!==Object.keys(e.dump).length?(function(e,t,n,r){var o,s,a,l,c,p,d="",f=e.tag,h=Object.keys(n);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new i("sortKeys must be a boolean or a function");for(o=0,s=h.length;o1024)&&(e.dump&&u===e.dump.charCodeAt(0)?p+="?":p+="? "),p+=e.dump,c&&(p+=z(e,t)),ie(e,t+1,l,!0,c)&&(e.dump&&u===e.dump.charCodeAt(0)?p+=":":p+=": ",d+=p+=e.dump));e.tag=f,e.dump=d||"{}"}(e,t,e.dump,o),h&&(e.dump="&ref_"+f+e.dump)):(function(e,t,n){var r,i,o,s,a,l="",c=e.tag,u=Object.keys(n);for(r=0,i=u.length;r1024&&(a+="? "),a+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),ie(e,t,s,!1,!1)&&(l+=a+=e.dump));e.tag=c,e.dump="{"+l+"}"}(e,t,e.dump),h&&(e.dump="&ref_"+f+" "+e.dump));else if("[object Array]"===p)r&&0!==e.dump.length?(e.noArrayIndent&&!l&&t>0?ne(e,t-1,e.dump,o):ne(e,t,e.dump,o),h&&(e.dump="&ref_"+f+e.dump)):(function(e,t,n){var r,i,o,s="",a=e.tag;for(r=0,i=n.length;r",e.dump=c+" "+e.dump)}return!0}function oe(e,t){var n,r,i=[],o=[];for(se(e,i,o),n=0,r=o.length;n>10),56320+(e-65536&1023))}for(var C=new Array(256),j=new Array(256),P=0;P<256;P++)C[P]=_(P)?1:0,j[P]=_(P);function T(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function I(e,t){var n={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return n.snippet=o(n),new i(t,n)}function R(e,t){throw I(e,t)}function N(e,t){e.onWarning&&e.onWarning.call(null,I(e,t))}var $={YAML:function(e,t,n){var r,i,o;null!==e.version&&R(e,"duplication of %YAML directive"),1!==n.length&&R(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&R(e,"ill-formed argument of the YAML directive"),i=parseInt(r[1],10),o=parseInt(r[2],10),1!==i&&R(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&N(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,i;2!==n.length&&R(e,"TAG directive accepts exactly two arguments"),r=n[0],i=n[1],b.test(r)||R(e,"ill-formed tag handle (first argument) of the TAG directive"),a.call(e.tagMap,r)&&R(e,'there is a previously declared suffix for "'+r+'" tag handle'),v.test(i)||R(e,"ill-formed tag prefix (second argument) of the TAG directive");try{i=decodeURIComponent(i)}catch(t){R(e,"tag prefix is malformed: "+i)}e.tagMap[r]=i}};function L(e,t,n,r){var i,o,s,a;if(t1&&(e.result+=r.repeat("\n",t-1))}function q(e,t){var n,r,i=e.tag,o=e.anchor,s=[],a=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=s),r=e.input.charCodeAt(e.position);0!==r&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,R(e,"tab characters must not be used in indentation")),45===r)&&S(e.input.charCodeAt(e.position+1));)if(a=!0,e.position++,z(e,!0,-1)&&e.lineIndent<=t)s.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,H(e,t,u,!1,!0),s.push(e.result),z(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)R(e,"bad indentation of a sequence entry");else if(e.lineIndentt?T=1:e.lineIndent===t?T=0:e.lineIndentt?T=1:e.lineIndent===t?T=0:e.lineIndentt)&&(v&&(s=e.line,a=e.lineStart,l=e.position),H(e,t,p,!0,i)&&(v?y=e.result:b=e.result),v||(M(e,h,m,g,y,b,s,a,l),g=y=b=null),z(e,!0,-1),u=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==u)R(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===o?R(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?R(e,"repeat of an indentation width identifier"):(p=t+o-1,u=!0)}if(k(s)){do{s=e.input.charCodeAt(++e.position)}while(k(s));if(35===s)do{s=e.input.charCodeAt(++e.position)}while(!w(s)&&0!==s)}for(;0!==s;){for(F(e),e.lineIndent=0,s=e.input.charCodeAt(e.position);(!u||e.lineIndentp&&(p=e.lineIndent),w(s))m++;else{if(e.lineIndent0){for(i=s,o=0;i>0;i--)(s=O(a=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+s:R(e,"expected hexadecimal character");e.result+=A(o),e.position++}else R(e,"unknown escape sequence");n=r=e.position}else w(a)?(L(e,n,r,!0),U(e,z(e,!1,t)),n=r=e.position):e.position===e.lineStart&&B(e)?R(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}R(e,"unexpected end of the stream within a double quoted scalar")}(e,_)?N=!0:function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!S(r)&&!E(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&R(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),a.call(e.anchorMap,n)||R(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],z(e,!0,-1),!0}(e)?(N=!0,null===e.tag&&null===e.anchor||R(e,"alias node should not have any properties")):function(e,t,n){var r,i,o,s,a,l,c,u,p=e.kind,d=e.result;if(S(u=e.input.charCodeAt(e.position))||E(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(S(r=e.input.charCodeAt(e.position+1))||n&&E(r)))return!1;for(e.kind="scalar",e.result="",i=o=e.position,s=!1;0!==u;){if(58===u){if(S(r=e.input.charCodeAt(e.position+1))||n&&E(r))break}else if(35===u){if(S(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&B(e)||n&&E(u))break;if(w(u)){if(a=e.line,l=e.lineStart,c=e.lineIndent,z(e,!1,-1),e.lineIndent>=t){s=!0,u=e.input.charCodeAt(e.position);continue}e.position=o,e.line=a,e.lineStart=l,e.lineIndent=c;break}}s&&(L(e,i,o,!1),U(e,e.line-a),i=o=e.position,s=!1),k(u)||(o=e.position+1),u=e.input.charCodeAt(++e.position)}return L(e,i,o,!1),!!e.result||(e.kind=p,e.result=d,!1)}(e,_,l===n)&&(N=!0,null===e.tag&&(e.tag="?")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===T&&(N=g&&q(e,P))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&R(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),y=0,b=e.implicitTypes.length;y"),null!==e.result&&x.kind!==e.kind&&R(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+x.kind+'", not "'+e.kind+'"'),x.resolve(e.result,e.tag)?(e.result=x.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):R(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||N}function Y(e){var t,n,r,i,o=e.position,s=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(i=e.input.charCodeAt(e.position))&&(z(e,!0,-1),i=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==i));){for(s=!0,i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!S(i);)i=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&R(e,"directive name must not be less than one character in length");0!==i;){for(;k(i);)i=e.input.charCodeAt(++e.position);if(35===i){do{i=e.input.charCodeAt(++e.position)}while(0!==i&&!w(i));break}if(w(i))break;for(t=e.position;0!==i&&!S(i);)i=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==i&&F(e),a.call($,n)?$[n](e,n,r):N(e,'unknown document directive "'+n+'"')}z(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,z(e,!0,-1)):s&&R(e,"directives end mark is expected"),H(e,e.lineIndent-1,p,!1,!0),z(e,!0,-1),e.checkLineBreaks&&g.test(e.input.slice(o,e.position))&&N(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&B(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,z(e,!0,-1)):e.positiona&&(t=r-a+(o=" ... ").length),n-r>a&&(n=r+a-(s=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+s,pos:r-t+o.length}}function o(e,t){return r.repeat(" ",t-e.length)+e}e.exports=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var n,s=/\r?\n|\r|\0/g,a=[0],l=[],c=-1;n=s.exec(e.buffer);)l.push(n.index),a.push(n.index+n[0].length),e.position<=n.index&&c<0&&(c=a.length-2);c<0&&(c=a.length-1);var u,p,d="",f=Math.min(e.line+t.linesAfter,l.length).toString().length,h=t.maxLength-(t.indent+f+3);for(u=1;u<=t.linesBefore&&!(c-u<0);u++)p=i(e.buffer,a[c-u],l[c-u],e.position-(a[c]-a[c-u]),h),d=r.repeat(" ",t.indent)+o((e.line-u+1).toString(),f)+" | "+p.str+"\n"+d;for(p=i(e.buffer,a[c],l[c],e.position,h),d+=r.repeat(" ",t.indent)+o((e.line+1).toString(),f)+" | "+p.str+"\n",d+=r.repeat("-",t.indent+f+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(c+u>=l.length);u++)p=i(e.buffer,a[c+u],l[c+u],e.position-(a[c]-a[c+u]),h),d+=r.repeat(" ",t.indent)+o((e.line+u+1).toString(),f)+" | "+p.str+"\n";return d.replace(/\n$/,"")}},5388:function(e,t,n){"use strict";var r=n(1231),i=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],o=["scalar","sequence","mapping"];e.exports=function(e,t){var n,s;if(t=t||{},Object.keys(t).forEach((function(t){if(-1===i.indexOf(t))throw new r('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=(n=t.styleAliases||null,s={},null!==n&&Object.keys(n).forEach((function(e){n[e].forEach((function(t){s[String(t)]=e}))})),s),-1===o.indexOf(this.kind))throw new r('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}},9342:function(e,t,n){"use strict";var r=n(5388),i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new r("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,s=i;for(n=0;n64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,r=e.replace(/[\r\n=]/g,""),o=r.length,s=i,a=0,l=[];for(t=0;t>16&255),l.push(a>>8&255),l.push(255&a)),a=a<<6|s.indexOf(r.charAt(t));return 0==(n=o%4*6)?(l.push(a>>16&255),l.push(a>>8&255),l.push(255&a)):18===n?(l.push(a>>10&255),l.push(a>>2&255)):12===n&&l.push(a>>4&255),new Uint8Array(l)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,n,r="",o=0,s=e.length,a=i;for(t=0;t>18&63],r+=a[o>>12&63],r+=a[o>>6&63],r+=a[63&o]),o=(o<<8)+e[t];return 0==(n=s%3)?(r+=a[o>>18&63],r+=a[o>>12&63],r+=a[o>>6&63],r+=a[63&o]):2===n?(r+=a[o>>10&63],r+=a[o>>4&63],r+=a[o<<2&63],r+=a[64]):1===n&&(r+=a[o>>2&63],r+=a[o<<4&63],r+=a[64],r+=a[64]),r}})},6199:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},1461:function(e,t,n){"use strict";var r=n(8433),i=n(5388),o=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"),s=/^[-+]?[0-9]+e/;e.exports=new i("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||r.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(r.isNegativeZero(e))return"-0.0";return n=e.toString(10),s.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},4466:function(e,t,n){"use strict";var r=n(8433),i=n(5388);function o(e){return 48<=e&&e<=55}function s(e){return 48<=e&&e<=57}e.exports=new i("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=e.length,i=0,a=!1;if(!r)return!1;if("-"!==(t=e[i])&&"+"!==t||(t=e[++i]),"0"===t){if(i+1===r)return!0;if("b"===(t=e[++i])){for(i++;i=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},2369:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},1851:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},9198:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"},empty:function(){return""}},defaultStyle:"lowercase"})},6946:function(e,t,n){"use strict";var r=n(5388),i=Object.prototype.hasOwnProperty,o=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,s,a,l=[],c=e;for(t=0,n=c.length;tc))return!1;var p=a.get(e);if(p&&a.get(t))return p==t;var d=-1,f=!0,h=n&o?new Ae:void 0;for(a.set(e,t),a.set(t,e);++d-1},Oe.prototype.set=function(e,t){var n=this.__data__,r=je(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},_e.prototype.clear=function(){this.size=0,this.__data__={hash:new Ee,map:new(de||Oe),string:new Ee}},_e.prototype.delete=function(e){var t=$e(this,e).delete(e);return this.size-=t?1:0,t},_e.prototype.get=function(e){return $e(this,e).get(e)},_e.prototype.has=function(e){return $e(this,e).has(e)},_e.prototype.set=function(e,t){var n=$e(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Ae.prototype.add=Ae.prototype.push=function(e){return this.__data__.set(e,r),this},Ae.prototype.has=function(e){return this.__data__.has(e)},Ce.prototype.clear=function(){this.__data__=new Oe,this.size=0},Ce.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Ce.prototype.get=function(e){return this.__data__.get(e)},Ce.prototype.has=function(e){return this.__data__.has(e)},Ce.prototype.set=function(e,t){var n=this.__data__;if(n instanceof Oe){var r=n.__data__;if(!de||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new _e(r)}return n.set(e,t),this.size=n.size,this};var De=le?function(e){return null==e?[]:(e=Object(e),function(t){for(var n=-1,r=null==t?0:t.length,i=0,o=[];++n-1&&e%1==0&&e-1&&e%1==0&&e<=s}function Ye(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function Ge(e){return null!=e&&"object"==typeof e}var Qe=F?function(e){return function(t){return e(t)}}(F):function(e){return Ge(e)&&He(e.length)&&!!P[Pe(e)]};function Xe(e){return null!=(t=e)&&He(t.length)&&!We(t)?function(e,t){var n=qe(e),r=!n&&Ue(e),i=!n&&!r&&Ve(e),o=!n&&!r&&!i&&Qe(e),s=n||r||i||o,a=s?function(e,t){for(var n=-1,r=Array(e);++n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach((function(t){var n=e.filter((function(e){return e.contains(t)})).length>0;-1!==e.indexOf(t)||n||e.push(t)})),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,s=function s(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",s),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",s),o=setTimeout(s,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,(function(){return!0}),(function(e){r++,n.waitForIframes(e.querySelector("html"),(function(){--r||t()}))}),(function(e){e||t()}))}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},s=t.querySelectorAll("iframe"),a=s.length,l=0;s=Array.prototype.slice.call(s);var c=function(){--a<=0&&o(l)};a||c(),s.forEach((function(t){e.matches(t,i.exclude)?c():i.onIframeReady(t,(function(e){n(t)&&(l++,r(e)),c()}),c)}))}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:(null===t||e.nextNode())&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach((function(e,t){e.val===n&&(i=t,o=e.handled)})),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach((function(e){e.handled||i.getIframeContents(e.val,(function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)}))}))}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o=this,s=this.createIterator(t,e,r),a=[],l=[],c=void 0,u=void 0;p=void 0,p=o.getIteratorNode(s),u=p.prevNode,c=p.node;)this.iframes&&this.forEachIframe(t,(function(e){return o.checkIframeFilter(c,u,e,a)}),(function(t){o.createInstanceOnIframe(t).forEachNode(e,(function(e){return l.push(e)}),r)})),l.push(c);var p;l.forEach((function(e){n(e)})),this.iframes&&this.handleOpenIframes(a,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),s=o.length;s||i(),o.forEach((function(o){var a=function(){r.iterateThroughNodes(e,o,t,n,(function(){--s<=0&&i()}))};r.iframes?r.waitForIframes(o,a):a()}))}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every((function(t){return!r.call(e,t)||(i=!0,!1)})),i}return!1}}]),e}(),o=function(){function o(e){t(this,o),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(o,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createRegExp",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),this.createAccuracyRegExp(e)}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==s&&""!==a&&(e=e.replace(new RegExp("("+this.escapeStr(s)+"|"+this.escapeStr(a)+")","gm"+n),r+"("+this.processSynomyms(s)+"|"+this.processSynomyms(a)+")"+r))}return e}},{key:"processSynomyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,(function(e){return"\\"===e.charAt(0)?"?":""}))).replace(/(?:\\)*\*/g,(function(e){return"\\"===e.charAt(0)?"*":""}))}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,(function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"}))}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach((function(i){n.every((function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0}))})),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="string"==typeof n?[]:n.limiters,o="";switch(i.forEach((function(e){o+="|"+t.escapeStr(e)})),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(o="\\s"+(o||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+o+"]*)";case"exactly":return"(^|\\s"+o+")("+e+")(?=$|\\s"+o+")"}}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach((function(e){t.opt.separateWordSearch?e.split(" ").forEach((function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)})):e.trim()&&-1===n.indexOf(e)&&n.push(e)})),{keywords:n.sort((function(e,t){return t.length-e.length})),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort((function(e,t){return e.start-t.start})).forEach((function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,s=i.end;i.valid&&(e.start=o,e.length=s-o,n.push(e),r=s)})),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,s=t-o,a=parseInt(e.start,10)-s;return(r=(a=a>o?o:a)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),a<0||r-a<0||a>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(a,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:a,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,(function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})}),(function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),(function(){e({value:n,nodes:r})}))}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),s=document.createElement(r);return s.setAttribute("data-markjs","true"),this.opt.className&&s.setAttribute("class",this.opt.className),s.textContent=i.textContent,i.parentNode.replaceChild(s,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every((function(s,a){var l=e.nodes[a+1];if(void 0===l||l.start>t){if(!r(s.node))return!1;var c=t-s.start,u=(n>s.end?s.end:n)-s.start,p=e.value.substr(0,s.start),d=e.value.substr(u+s.start);if(s.node=o.wrapRangeInTextNode(s.node,c,u),e.value=p+d,e.nodes.forEach((function(t,n){n>=a&&(e.nodes[n].start>0&&n!==a&&(e.nodes[n].start-=u),e.nodes[n].end-=u)})),n-=u,i(s.node.previousSibling,s.start),!(n>s.end))return!1;t=s.end}return!0}))}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,s=0===t?0:t+1;this.getTextNodes((function(t){t.nodes.forEach((function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[s];)if(n(i[s],t)){var a=i.index;if(0!==s)for(var l=1;l1&&console.warn("Replacing with",t),m++}}else{let i=u(l(t,e[n]));if(s.verbose>1&&console.warn((!1===i?f.colour.red:f.colour.green)+"Fragment resolution",e[n],f.colour.normal),!1===i){if(r.parent[r.pkey]={},s.fatal){let t=new Error("Fragment $ref resolution failed "+e[n]);if(!s.promise)throw t;s.promise.reject(t)}}else m++,r.parent[r.pkey]=i,h[e[n]]=r.path.replace("/%24ref","")}else if(p.protocol){let t=o.resolve(i,e[n]).toString();s.verbose>1&&console.warn(f.colour.yellow+"Rewriting external url ref",e[n],"as",t,f.colour.normal),e["x-miro"]=e[n],s.externalRefs[e[n]]&&(s.externalRefs[t]||(s.externalRefs[t]=s.externalRefs[e[n]]),s.externalRefs[t].failed=s.externalRefs[e[n]].failed),e[n]=t}else if(!e["x-miro"]){let t=o.resolve(i,e[n]).toString(),r=!1;s.externalRefs[e[n]]&&(r=s.externalRefs[e[n]].failed),r||(s.verbose>1&&console.warn(f.colour.yellow+"Rewriting external ref",e[n],"as",t,f.colour.normal),e["x-miro"]=e[n],e[n]=t)}}));return c(e,{},(function(e,t,n){d(e,t)&&void 0!==e.$fixed&&delete e.$fixed})),s.verbose>1&&console.warn("Finished fragment resolution"),e}function m(e,t){if(!t.filters||!t.filters.length)return e;for(let n of t.filters)e=n(e,t);return e}function g(e,t,n,s){var c=o.parse(n.source),p=n.source.split("\\").join("/").split("/");p.pop()||p.pop();let d="",f=t.split("#");f.length>1&&(d="#"+f[1],t=f[0]),p=p.join("/");let g=(y=o.parse(t).protocol,b=c.protocol,y&&y.length>2?y:b&&b.length>2?b:"file:");var y,b;let v;if(v="file:"===g?i.resolve(p?p+"/":"",t):o.resolve(p?p+"/":"",t),n.cache[v]){n.verbose&&console.warn("CACHED",v,d);let e=u(n.cache[v]),r=n.externalRef=e;if(d&&(r=l(r,d),!1===r&&(r={},n.fatal))){let e=new Error("Cached $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}return r=h(r,e,t,d,v,n),r=m(r,n),s(u(r),v,n),Promise.resolve(r)}if(n.verbose&&console.warn("GET",v,d),n.handlers&&n.handlers[g])return n.handlers[g](p,t,d,n).then((function(e){return n.externalRef=e,e=m(e,n),n.cache[v]=e,s(e,v,n),e})).catch((function(e){throw n.verbose&&console.warn(e),e}));if(g&&g.startsWith("http")){const e=Object.assign({},n.fetchOptions,{agent:n.agent});return n.fetch(v,e).then((function(e){if(200!==e.status){if(n.ignoreIOErrors)return n.verbose&&console.warn("FAILED",t),n.externalRefs[t].failed=!0,'{"$ref":"'+t+'"}';throw new Error(`Received status code ${e.status}: ${v}`)}return e.text()})).then((function(e){try{let r=a.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[v]=u(e),d&&!1===(e=l(e,d))&&(e={},n.fatal)){let e=new Error("Remote $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}e=m(e=h(e,r,t,d,v,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return s(e,v,n),e})).catch((function(e){if(n.verbose&&console.warn(e),n.cache[v]={},!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}{const e='{"$ref":"'+t+'"}';return function(e,t,n,i,o){return new Promise((function(s,a){r.readFile(e,t,(function(e,t){e?n.ignoreIOErrors&&o?(n.verbose&&console.warn("FAILED",i),n.externalRefs[i].failed=!0,s(o)):a(e):s(t)}))}))}(v,n.encoding||"utf8",n,t,e).then((function(e){try{let r=a.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[v]=u(e),d&&!1===(e=l(e,d))&&(e={},n.fatal)){let e=new Error("File $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}e=m(e=h(e,r,t,d,v,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return s(e,v,n),e})).catch((function(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}}function y(e){return new Promise((function(t,n){(function(e){return new Promise((function(t,n){function r(t,n,r){if(t[n]&&d(t[n],"$ref")){let o=t[n].$ref;if(!o.startsWith("#")){let s="";if(!i[o]){let t=Object.keys(i).find((function(e,t,n){return o.startsWith(e+"/")}));t&&(e.verbose&&console.warn("Found potential subschema at",t),s="/"+(o.split("#")[1]||"").replace(t.split("#")[1]||""),s=s.split("/undefined").join(""),o=t)}if(i[o]||(i[o]={resolved:!1,paths:[],extras:{},description:t[n].description}),i[o].resolved)if(i[o].failed);else if(e.rewriteRefs){let r=i[o].resolvedAt;e.verbose>1&&console.warn("Rewriting ref",o,r),t[n]["x-miro"]=o,t[n].$ref=r+s}else t[n]=u(i[o].data);else i[o].paths.push(r.path),i[o].extras[r.path]=s}}}let i=e.externalRefs;if(e.resolver.depth>0&&e.source===e.resolver.base)return t(i);c(e.openapi.definitions,{identityDetection:!0,path:"#/definitions"},r),c(e.openapi.components,{identityDetection:!0,path:"#/components"},r),c(e.openapi,{identityDetection:!0},r),t(i)}))})(e).then((function(t){for(let n in t)if(!t[n].resolved){let r=e.resolver.depth;r>0&&r++,e.resolver.actions[r].push((function(){return g(e.openapi,n,e,(function(e,r,i){if(!t[n].resolved){let o={};o.context=t[n],o.$ref=n,o.original=u(e),o.updated=e,o.source=r,i.externals.push(o),t[n].resolved=!0}let o=Object.assign({},i,{source:"",resolver:{actions:i.resolver.actions,depth:i.resolver.actions.length-1,base:i.resolver.base}});i.patch&&t[n].description&&!e.description&&"object"==typeof e&&(e.description=t[n].description),t[n].data=e;let s=(a=t[n].paths,[...new Set(a)]);var a;s=s.sort((function(e,t){const n=e.startsWith("#/components/")||e.startsWith("#/definitions/"),r=t.startsWith("#/components/")||t.startsWith("#/definitions/");return n&&!r?-1:r&&!n?1:0}));for(let r of s)if(t[n].resolvedAt&&r!==t[n].resolvedAt&&r.indexOf("x-ms-examples/")<0)i.verbose>1&&console.warn("Creating pointer to data at",r),l(i.openapi,r,{$ref:t[n].resolvedAt+t[n].extras[r],"x-miro":n+t[n].extras[r]});else{t[n].resolvedAt?i.verbose>1&&console.warn("Avoiding circular reference"):(t[n].resolvedAt=r,i.verbose>1&&console.warn("Creating initial clone of data at",r));let o=u(e);l(i.openapi,r,o)}0===i.resolver.actions[o.resolver.depth].length&&i.resolver.actions[o.resolver.depth].push((function(){return y(o)}))}))}))}})).catch((function(t){e.verbose&&console.warn(t),n(t)}));let r={options:e};r.actions=e.resolver.actions[e.resolver.depth],t(r)}))}function b(e,t,n){e.resolver.actions.push([]),y(e).then((function(r){var i;(i=r.actions,i.reduce(((e,t)=>e.then((e=>t().then(Array.prototype.concat.bind(e))))),Promise.resolve([]))).then((function(){if(e.resolver.depth>=e.resolver.actions.length)return console.warn("Ran off the end of resolver actions"),t(!0);e.resolver.depth++,e.resolver.actions[e.resolver.depth].length?setTimeout((function(){b(r.options,t,n)}),0):(e.verbose>1&&console.warn(f.colour.yellow+"Finished external resolution!",f.colour.normal),e.resolveInternal&&(e.verbose>1&&console.warn(f.colour.yellow+"Starting internal resolution!",f.colour.normal),e.openapi=p(e.openapi,e.original,{verbose:e.verbose-1}),e.verbose>1&&console.warn(f.colour.yellow+"Finished internal resolution!",f.colour.normal)),c(e.openapi,{},(function(t,n,r){d(t,n)&&(e.preserveMiro||delete t["x-miro"])})),t(e))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))}function v(e){if(e.cache||(e.cache={}),e.fetch||(e.fetch=s),e.source){let t=o.parse(e.source);(!t.protocol||t.protocol.length<=2)&&(e.source=i.resolve(e.source))}e.externals=[],e.externalRefs={},e.rewriteRefs=!0,e.resolver={},e.resolver.depth=0,e.resolver.base=e.source,e.resolver.actions=[[]]}e.exports={optionalResolve:function(e){return v(e),new Promise((function(t,n){e.resolve?b(e,t,n):t(e)}))},resolve:function(e,t,n){return n||(n={}),n.openapi=e,n.source=t,n.resolve=!0,v(n),new Promise((function(e,t){b(n,e,t)}))}}},1319:function(e){"use strict";function t(){return{depth:0,seen:new WeakMap,top:!0,combine:!1,allowRefSiblings:!1}}e.exports={getDefaultState:t,walkSchema:function e(n,r,i,o){if(void 0===i.depth&&(i=t()),null==n)return n;if(void 0!==n.$ref){let e={$ref:n.$ref};return i.allowRefSiblings&&n.description&&(e.description=n.description),o(e,r,i),e}if(i.combine&&(n.allOf&&Array.isArray(n.allOf)&&1===n.allOf.length&&delete(n=Object.assign({},n.allOf[0],n)).allOf,n.anyOf&&Array.isArray(n.anyOf)&&1===n.anyOf.length&&delete(n=Object.assign({},n.anyOf[0],n)).anyOf,n.oneOf&&Array.isArray(n.oneOf)&&1===n.oneOf.length&&delete(n=Object.assign({},n.oneOf[0],n)).oneOf),o(n,r,i),i.seen.has(n))return n;if("object"==typeof n&&null!==n&&i.seen.set(n,!0),i.top=!1,i.depth++,void 0!==n.items&&(i.property="items",e(n.items,n,i,o)),n.additionalItems&&"object"==typeof n.additionalItems&&(i.property="additionalItems",e(n.additionalItems,n,i,o)),n.additionalProperties&&"object"==typeof n.additionalProperties&&(i.property="additionalProperties",e(n.additionalProperties,n,i,o)),n.properties)for(let t in n.properties){let r=n.properties[t];i.property="properties/"+t,e(r,n,i,o)}if(n.patternProperties)for(let t in n.patternProperties){let r=n.patternProperties[t];i.property="patternProperties/"+t,e(r,n,i,o)}if(n.allOf)for(let t in n.allOf){let r=n.allOf[t];i.property="allOf/"+t,e(r,n,i,o)}if(n.anyOf)for(let t in n.anyOf){let r=n.anyOf[t];i.property="anyOf/"+t,e(r,n,i,o)}if(n.oneOf)for(let t in n.oneOf){let r=n.oneOf[t];i.property="oneOf/"+t,e(r,n,i,o)}return n.not&&(i.property="not",e(n.not,n,i,o)),i.depth--,n}}},7975:function(e){"use strict";function t(e){if("string"!=typeof e)throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}function n(e,t){for(var n,r="",i=0,o=-1,s=0,a=0;a<=e.length;++a){if(a2){var l=r.lastIndexOf("/");if(l!==r.length-1){-1===l?(r="",i=0):i=(r=r.slice(0,l)).length-1-r.lastIndexOf("/"),o=a,s=0;continue}}else if(2===r.length||1===r.length){r="",i=0,o=a,s=0;continue}t&&(r.length>0?r+="/..":r="..",i=2)}else r.length>0?r+="/"+e.slice(o+1,a):r=e.slice(o+1,a),i=a-o-1;o=a,s=0}else 46===n&&-1!==s?++s:s=-1}return r}var r={resolve:function(){for(var e,r="",i=!1,o=arguments.length-1;o>=-1&&!i;o--){var s;o>=0?s=arguments[o]:(void 0===e&&(e=process.cwd()),s=e),t(s),0!==s.length&&(r=s+"/"+r,i=47===s.charCodeAt(0))}return r=n(r,!i),i?r.length>0?"/"+r:"/":r.length>0?r:"."},normalize:function(e){if(t(e),0===e.length)return".";var r=47===e.charCodeAt(0),i=47===e.charCodeAt(e.length-1);return 0!==(e=n(e,!r)).length||r||(e="."),e.length>0&&i&&(e+="/"),r?"/"+e:e},isAbsolute:function(e){return t(e),e.length>0&&47===e.charCodeAt(0)},join:function(){if(0===arguments.length)return".";for(var e,n=0;n0&&(void 0===e?e=i:e+="/"+i)}return void 0===e?".":r.normalize(e)},relative:function(e,n){if(t(e),t(n),e===n)return"";if((e=r.resolve(e))===(n=r.resolve(n)))return"";for(var i=1;ic){if(47===n.charCodeAt(a+p))return n.slice(a+p+1);if(0===p)return n.slice(a+p)}else s>c&&(47===e.charCodeAt(i+p)?u=p:0===p&&(u=0));break}var d=e.charCodeAt(i+p);if(d!==n.charCodeAt(a+p))break;47===d&&(u=p)}var f="";for(p=i+u+1;p<=o;++p)p!==o&&47!==e.charCodeAt(p)||(0===f.length?f+="..":f+="/..");return f.length>0?f+n.slice(a+u):(a+=u,47===n.charCodeAt(a)&&++a,n.slice(a))},_makeLong:function(e){return e},dirname:function(e){if(t(e),0===e.length)return".";for(var n=e.charCodeAt(0),r=47===n,i=-1,o=!0,s=e.length-1;s>=1;--s)if(47===(n=e.charCodeAt(s))){if(!o){i=s;break}}else o=!1;return-1===i?r?"/":".":r&&1===i?"//":e.slice(0,i)},basename:function(e,n){if(void 0!==n&&"string"!=typeof n)throw new TypeError('"ext" argument must be a string');t(e);var r,i=0,o=-1,s=!0;if(void 0!==n&&n.length>0&&n.length<=e.length){if(n.length===e.length&&n===e)return"";var a=n.length-1,l=-1;for(r=e.length-1;r>=0;--r){var c=e.charCodeAt(r);if(47===c){if(!s){i=r+1;break}}else-1===l&&(s=!1,l=r+1),a>=0&&(c===n.charCodeAt(a)?-1==--a&&(o=r):(a=-1,o=l))}return i===o?o=l:-1===o&&(o=e.length),e.slice(i,o)}for(r=e.length-1;r>=0;--r)if(47===e.charCodeAt(r)){if(!s){i=r+1;break}}else-1===o&&(s=!1,o=r+1);return-1===o?"":e.slice(i,o)},extname:function(e){t(e);for(var n=-1,r=0,i=-1,o=!0,s=0,a=e.length-1;a>=0;--a){var l=e.charCodeAt(a);if(47!==l)-1===i&&(o=!1,i=a+1),46===l?-1===n?n=a:1!==s&&(s=1):-1!==n&&(s=-1);else if(!o){r=a+1;break}}return-1===n||-1===i||0===s||1===s&&n===i-1&&n===r+1?"":e.slice(n,i)},format:function(e){if(null===e||"object"!=typeof e)throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof e);return function(e,t){var n=t.dir||t.root,r=t.base||(t.name||"")+(t.ext||"");return n?n===t.root?n+r:n+"/"+r:r}(0,e)},parse:function(e){t(e);var n={root:"",dir:"",base:"",ext:"",name:""};if(0===e.length)return n;var r,i=e.charCodeAt(0),o=47===i;o?(n.root="/",r=1):r=0;for(var s=-1,a=0,l=-1,c=!0,u=e.length-1,p=0;u>=r;--u)if(47!==(i=e.charCodeAt(u)))-1===l&&(c=!1,l=u+1),46===i?-1===s?s=u:1!==p&&(p=1):-1!==s&&(p=-1);else if(!c){a=u+1;break}return-1===s||-1===l||0===p||1===p&&s===l-1&&s===a+1?-1!==l&&(n.base=n.name=0===a&&o?e.slice(1,l):e.slice(a,l)):(0===a&&o?(n.name=e.slice(1,s),n.base=e.slice(1,l)):(n.name=e.slice(a,s),n.base=e.slice(a,l)),n.ext=e.slice(s,l)),a>0?n.dir=e.slice(0,a-1):o&&(n.dir="/"),n},sep:"/",delimiter:":",win32:null,posix:null};r.posix=r,e.exports=r},5127:function(e){e.exports=function(){var e=[],t=[],n={},r={},i={};function o(e){return"string"==typeof e?new RegExp("^"+e+"$","i"):e}function s(e,t){return e===t?t:e===e.toLowerCase()?t.toLowerCase():e===e.toUpperCase()?t.toUpperCase():e[0]===e[0].toUpperCase()?t.charAt(0).toUpperCase()+t.substr(1).toLowerCase():t.toLowerCase()}function a(e,t){return e.replace(t[0],(function(n,r){var i,o,a=(i=t[1],o=arguments,i.replace(/\$(\d{1,2})/g,(function(e,t){return o[t]||""})));return s(""===n?e[r-1]:n,a)}))}function l(e,t,r){if(!e.length||n.hasOwnProperty(e))return t;for(var i=r.length;i--;){var o=r[i];if(o[0].test(t))return a(t,o)}return t}function c(e,t,n){return function(r){var i=r.toLowerCase();return t.hasOwnProperty(i)?s(r,i):e.hasOwnProperty(i)?s(r,e[i]):l(i,r,n)}}function u(e,t,n,r){return function(r){var i=r.toLowerCase();return!!t.hasOwnProperty(i)||!e.hasOwnProperty(i)&&l(i,i,n)===i}}function p(e,t,n){return(n?t+" ":"")+(1===t?p.singular(e):p.plural(e))}return p.plural=c(i,r,e),p.isPlural=u(i,r,e),p.singular=c(r,i,t),p.isSingular=u(r,i,t),p.addPluralRule=function(t,n){e.push([o(t),n])},p.addSingularRule=function(e,n){t.push([o(e),n])},p.addUncountableRule=function(e){"string"!=typeof e?(p.addPluralRule(e,"$0"),p.addSingularRule(e,"$0")):n[e.toLowerCase()]=!0},p.addIrregularRule=function(e,t){t=t.toLowerCase(),e=e.toLowerCase(),i[e]=t,r[t]=e},[["I","we"],["me","us"],["he","they"],["she","they"],["them","them"],["myself","ourselves"],["yourself","yourselves"],["itself","themselves"],["herself","themselves"],["himself","themselves"],["themself","themselves"],["is","are"],["was","were"],["has","have"],["this","these"],["that","those"],["echo","echoes"],["dingo","dingoes"],["volcano","volcanoes"],["tornado","tornadoes"],["torpedo","torpedoes"],["genus","genera"],["viscus","viscera"],["stigma","stigmata"],["stoma","stomata"],["dogma","dogmata"],["lemma","lemmata"],["schema","schemata"],["anathema","anathemata"],["ox","oxen"],["axe","axes"],["die","dice"],["yes","yeses"],["foot","feet"],["eave","eaves"],["goose","geese"],["tooth","teeth"],["quiz","quizzes"],["human","humans"],["proof","proofs"],["carve","carves"],["valve","valves"],["looey","looies"],["thief","thieves"],["groove","grooves"],["pickaxe","pickaxes"],["passerby","passersby"]].forEach((function(e){return p.addIrregularRule(e[0],e[1])})),[[/s?$/i,"s"],[/[^\u0000-\u007F]$/i,"$0"],[/([^aeiou]ese)$/i,"$1"],[/(ax|test)is$/i,"$1es"],[/(alias|[^aou]us|t[lm]as|gas|ris)$/i,"$1es"],[/(e[mn]u)s?$/i,"$1s"],[/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i,"$1"],[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i,"$1i"],[/(alumn|alg|vertebr)(?:a|ae)$/i,"$1ae"],[/(seraph|cherub)(?:im)?$/i,"$1im"],[/(her|at|gr)o$/i,"$1oes"],[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i,"$1a"],[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i,"$1a"],[/sis$/i,"ses"],[/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i,"$1$2ves"],[/([^aeiouy]|qu)y$/i,"$1ies"],[/([^ch][ieo][ln])ey$/i,"$1ies"],[/(x|ch|ss|sh|zz)$/i,"$1es"],[/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i,"$1ices"],[/\b((?:tit)?m|l)(?:ice|ouse)$/i,"$1ice"],[/(pe)(?:rson|ople)$/i,"$1ople"],[/(child)(?:ren)?$/i,"$1ren"],[/eaux$/i,"$0"],[/m[ae]n$/i,"men"],["thou","you"]].forEach((function(e){return p.addPluralRule(e[0],e[1])})),[[/s$/i,""],[/(ss)$/i,"$1"],[/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i,"$1fe"],[/(ar|(?:wo|[ae])l|[eo][ao])ves$/i,"$1f"],[/ies$/i,"y"],[/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i,"$1ie"],[/\b(mon|smil)ies$/i,"$1ey"],[/\b((?:tit)?m|l)ice$/i,"$1ouse"],[/(seraph|cherub)im$/i,"$1"],[/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i,"$1"],[/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i,"$1sis"],[/(movie|twelve|abuse|e[mn]u)s$/i,"$1"],[/(test)(?:is|es)$/i,"$1is"],[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i,"$1us"],[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i,"$1um"],[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i,"$1on"],[/(alumn|alg|vertebr)ae$/i,"$1a"],[/(cod|mur|sil|vert|ind)ices$/i,"$1ex"],[/(matr|append)ices$/i,"$1ix"],[/(pe)(rson|ople)$/i,"$1rson"],[/(child)ren$/i,"$1"],[/(eau)x?$/i,"$1"],[/men$/i,"man"]].forEach((function(e){return p.addSingularRule(e[0],e[1])})),["adulthood","advice","agenda","aid","aircraft","alcohol","ammo","analytics","anime","athletics","audio","bison","blood","bream","buffalo","butter","carp","cash","chassis","chess","clothing","cod","commerce","cooperation","corps","debris","diabetes","digestion","elk","energy","equipment","excretion","expertise","firmware","flounder","fun","gallows","garbage","graffiti","hardware","headquarters","health","herpes","highjinks","homework","housework","information","jeans","justice","kudos","labour","literature","machinery","mackerel","mail","media","mews","moose","music","mud","manga","news","only","personnel","pike","plankton","pliers","police","pollution","premises","rain","research","rice","salmon","scissors","series","sewage","shambles","shrimp","software","species","staff","swine","tennis","traffic","transportation","trout","tuna","wealth","welfare","whiting","wildebeest","wildlife","you",/pok[eé]mon$/i,/[^aeiou]ese$/i,/deer$/i,/fish$/i,/measles$/i,/o[iu]s$/i,/pox$/i,/sheep$/i].forEach(p.addUncountableRule),p}()},7022:function(){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var i=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=r.variable[1].inside,s=0;s>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean},5624:function(){Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}},4511:function(){!function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(Prism)},2415:function(){!function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(Prism)},5651:function(){!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var i="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",o="class enum interface record struct",s="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",a="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var c=l(o),u=RegExp(l(i+" "+o+" "+s+" "+a)),p=l(o+" "+s+" "+a),d=l(i+" "+o+" "+a),f=r(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),h=r(/\((?:[^()]|<>)*\)/.source,2),m=/@?\b[A-Za-z_]\w*\b/.source,g=t(/<<0>>(?:\s*<<1>>)?/.source,[m,f]),y=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[p,g]),b=/\[\s*(?:,\s*)*\]/.source,v=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[y,b]),x=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[f,h,b]),w=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[x]),k=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[w,y,b]),S={keyword:u,punctuation:/[<>()?,.:[\]]/},E=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,O=/"(?:\\.|[^\\"\r\n])*"/.source,_=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[_]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[O]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[y]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[m,k]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[m]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[c,g]),lookbehind:!0,inside:S},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[y]),lookbehind:!0,inside:S},{pattern:n(/(\bwhere\s+)<<0>>/.source,[m]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[v]),lookbehind:!0,inside:S},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[k,d,m]),inside:S}],keyword:u,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[m]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[m]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[h]),lookbehind:!0,alias:"class-name",inside:S},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[k,y]),inside:S,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[k]),lookbehind:!0,inside:S,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[m,f]),inside:{function:n(/^<<0>>/.source,[m]),generic:{pattern:RegExp(f),alias:"class-name",inside:S}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[c,g,m,k,u.source,h,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:n(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[g,h]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:u,"class-name":{pattern:RegExp(k),greedy:!0,inside:S},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var A=O+"|"+E,C=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[A]),j=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[C]),2),P=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,T=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[y,j]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[P,T]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[P]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[j]),inside:e.languages.csharp},"class-name":{pattern:RegExp(y),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var I=/:[^}\r\n]+/.source,R=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[C]),2),N=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[R,I]),$=r(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[A]),2),L=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[$,I]);function D(t,r){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[r,I]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[N]),lookbehind:!0,greedy:!0,inside:D(N,R)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[L]),lookbehind:!0,greedy:!0,inside:D(L,$)}],char:{pattern:RegExp(E),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism)},2630:function(){Prism.languages.csv={value:/[^\r\n,"]+|"(?:[^"]|"")*"(?!")/,punctuation:/,/}},6378:function(){Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),Prism.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete Prism.languages.go["class-name"]},4784:function(){!function(e){function t(e){return RegExp("(^(?:"+e+"):[ \t]*(?![ \t]))[^]+","i")}e.languages.http={"request-line":{pattern:/^(?:CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PRI|PUT|SEARCH|TRACE)\s(?:https?:\/\/|\/)\S*\sHTTP\/[\d.]+/m,inside:{method:{pattern:/^[A-Z]+\b/,alias:"property"},"request-target":{pattern:/^(\s)(?:https?:\/\/|\/)\S*(?=\s)/,lookbehind:!0,alias:"url",inside:e.languages.uri},"http-version":{pattern:/^(\s)HTTP\/[\d.]+/,lookbehind:!0,alias:"property"}}},"response-status":{pattern:/^HTTP\/[\d.]+ \d+ .+/m,inside:{"http-version":{pattern:/^HTTP\/[\d.]+/,alias:"property"},"status-code":{pattern:/^(\s)\d+(?=\s)/,lookbehind:!0,alias:"number"},"reason-phrase":{pattern:/^(\s).+/,lookbehind:!0,alias:"string"}}},header:{pattern:/^[\w-]+:.+(?:(?:\r\n?|\n)[ \t].+)*/m,inside:{"header-value":[{pattern:t(/Content-Security-Policy/.source),lookbehind:!0,alias:["csp","languages-csp"],inside:e.languages.csp},{pattern:t(/Public-Key-Pins(?:-Report-Only)?/.source),lookbehind:!0,alias:["hpkp","languages-hpkp"],inside:e.languages.hpkp},{pattern:t(/Strict-Transport-Security/.source),lookbehind:!0,alias:["hsts","languages-hsts"],inside:e.languages.hsts},{pattern:t(/[^:]+/.source),lookbehind:!0}],"header-name":{pattern:/^[^:]+/,alias:"keyword"},punctuation:/^:/}}};var n,r=e.languages,i={"application/javascript":r.javascript,"application/json":r.json||r.javascript,"application/xml":r.xml,"text/xml":r.xml,"text/html":r.html,"text/css":r.css,"text/plain":r.plain},o={"application/json":!0,"application/xml":!0};function s(e){var t=e.replace(/^[a-z]+\//,"");return"(?:"+e+"|\\w+/(?:[\\w.-]+\\+)+"+t+"(?![+\\w.-]))"}for(var a in i)if(i[a]){n=n||{};var l=o[a]?s(a):a;n[a.replace(/\//g,"-")]={pattern:RegExp("("+/content-type:\s*/.source+l+/(?:(?:\r\n?|\n)[\w-].*)*(?:\r(?:\n|(?!\n))|\n)/.source+")"+/[^ \t\w-][\s\S]*/.source,"i"),lookbehind:!0,inside:i[a]}}n&&e.languages.insertBefore("http","header",n)}(Prism)},6976:function(){!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,r={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[r,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:r.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:r.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":r,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:r.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:r.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)},64:function(){Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}},9700:function(){!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,i,o){if(n.language===r){var s=n.tokenStack=[];n.code=n.code.replace(i,(function(e){if("function"==typeof o&&!o(e))return e;for(var i,a=s.length;-1!==n.code.indexOf(i=t(r,a));)++a;return s[a]=e,i})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var i=0,o=Object.keys(n.tokenStack);!function s(a){for(var l=0;l=o.length);l++){var c=a[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[i],p=n.tokenStack[u],d="string"==typeof c?c:c.content,f=t(r,u),h=d.indexOf(f);if(h>-1){++i;var m=d.substring(0,h),g=new e.Token(r,e.tokenize(p,n.grammar),"language-"+r,p),y=d.substring(h+f.length),b=[];m&&b.push.apply(b,s([m])),b.push(g),y&&b.push.apply(b,s([y])),"string"==typeof c?a.splice.apply(a,[l,1].concat(b)):c.content=b}}else c.content&&s(c.content)}return a}(n.tokens)}}}})}(Prism)},4312:function(){Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^$/i;var r={"included-cdata":{pattern://i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]};var i={};i[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:r},Prism.languages.insertBefore("markup","cdata",i)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,t){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:Prism.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml},596:function(){Prism.languages.objectivec=Prism.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete Prism.languages.objectivec["class-name"],Prism.languages.objc=Prism.languages.objectivec},2821:function(){!function(e){var t=/(?:\((?:[^()\\]|\\[\s\S])*\)|\{(?:[^{}\\]|\\[\s\S])*\}|\[(?:[^[\]\\]|\\[\s\S])*\]|<(?:[^<>\\]|\\[\s\S])*>)/.source;e.languages.perl={comment:[{pattern:/(^\s*)=\w[\s\S]*?=cut.*/m,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0,greedy:!0}],string:[{pattern:RegExp(/\b(?:q|qq|qw|qx)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,t].join("|")+")"),greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:RegExp(/\b(?:m|qr)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,t].join("|")+")"+/[msixpodualngc]*/.source),greedy:!0},{pattern:RegExp(/(^|[^-])\b(?:s|tr|y)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,/([a-zA-Z0-9])(?:(?!\3)[^\\]|\\[\s\S])*\3(?:(?!\3)[^\\]|\\[\s\S])*\3/.source,t+/\s*/.source+t].join("|")+")"+/[msixpodualngcer]*/.source),lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|x|xor)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+(?![\w$]))+(?:::)*/,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*?>|\b_\b/,alias:"symbol"},"v-string":{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/(\bsub[ \t]+)\w+/,lookbehind:!0},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|xor)\b/,punctuation:/[{}[\];(),:]/}}(Prism)},3554:function(){!function(e){var t=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],r=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,i=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,o=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:t,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:r,operator:i,punctuation:o};var s={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},a=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:s}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:s}}];e.languages.insertBefore("php","variable",{string:a,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:t,string:a,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:r,operator:i,punctuation:o}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(t){/<\?/.test(t.code)&&e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(Prism)},2342:function(){Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python},4113:function(){Prism.languages.q={string:/"(?:\\.|[^"\\\r\n])*"/,comment:[{pattern:/([\t )\]}])\/.*/,lookbehind:!0,greedy:!0},{pattern:/(^|\r?\n|\r)\/[\t ]*(?:(?:\r?\n|\r)(?:.*(?:\r?\n|\r(?!\n)))*?(?:\\(?=[\t ]*(?:\r?\n|\r))|$)|\S.*)/,lookbehind:!0,greedy:!0},{pattern:/^\\[\t ]*(?:\r?\n|\r)[\s\S]+/m,greedy:!0},{pattern:/^#!.+/m,greedy:!0}],symbol:/`(?::\S+|[\w.]*)/,datetime:{pattern:/0N[mdzuvt]|0W[dtz]|\d{4}\.\d\d(?:m|\.\d\d(?:T(?:\d\d(?::\d\d(?::\d\d(?:[.:]\d\d\d)?)?)?)?)?[dz]?)|\d\d:\d\d(?::\d\d(?:[.:]\d\d\d)?)?[uvt]?/,alias:"number"},number:/\b(?![01]:)(?:0N[hje]?|0W[hj]?|0[wn]|0x[\da-fA-F]+|\d+(?:\.\d*)?(?:e[+-]?\d+)?[hjfeb]?)/,keyword:/\\\w+\b|\b(?:abs|acos|aj0?|all|and|any|asc|asin|asof|atan|attr|avgs?|binr?|by|ceiling|cols|cor|cos|count|cov|cross|csv|cut|delete|deltas|desc|dev|differ|distinct|div|do|dsave|ej|enlist|eval|except|exec|exit|exp|fby|fills|first|fkeys|flip|floor|from|get|getenv|group|gtime|hclose|hcount|hdel|hopen|hsym|iasc|identity|idesc|if|ij|in|insert|inter|inv|keys?|last|like|list|ljf?|load|log|lower|lsq|ltime|ltrim|mavg|maxs?|mcount|md5|mdev|med|meta|mins?|mmax|mmin|mmu|mod|msum|neg|next|not|null|or|over|parse|peach|pj|plist|prds?|prev|prior|rand|rank|ratios|raze|read0|read1|reciprocal|reval|reverse|rload|rotate|rsave|rtrim|save|scan|scov|sdev|select|set|setenv|show|signum|sin|sqrt|ssr?|string|sublist|sums?|sv|svar|system|tables|tan|til|trim|txf|type|uj|ungroup|union|update|upper|upsert|value|var|views?|vs|wavg|where|while|within|wj1?|wsum|ww|xasc|xbar|xcols?|xdesc|xexp|xgroup|xkey|xlog|xprev|xrank)\b/,adverb:{pattern:/['\/\\]:?|\beach\b/,alias:"function"},verb:{pattern:/(?:\B\.\B|\b[01]:|<[=>]?|>=?|[:+\-*%,!?~=|$&#@^]):?|\b_\b:?/,alias:"operator"},punctuation:/[(){}\[\];.]/}},1648:function(){!function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var t={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var n="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",r=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+n+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+r),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+r+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+n),greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+n),greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism)},4252:function(){Prism.languages.scala=Prism.languages.extend("java",{"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/<-|=>|\b(?:abstract|case|catch|class|def|derives|do|else|enum|extends|extension|final|finally|for|forSome|given|if|implicit|import|infix|inline|lazy|match|new|null|object|opaque|open|override|package|private|protected|return|sealed|self|super|this|throw|trait|transparent|try|type|using|val|var|while|with|yield)\b/,number:/\b0x(?:[\da-f]*\.)?[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e\d+)?[dfl]?/i,builtin:/\b(?:Any|AnyRef|AnyVal|Boolean|Byte|Char|Double|Float|Int|Long|Nothing|Short|String|Unit)\b/,symbol:/'[^\d\s\\]\w*/}),Prism.languages.insertBefore("scala","triple-quoted-string",{"string-interpolation":{pattern:/\b[a-z]\w*(?:"""(?:[^$]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*?"""|"(?:[^$"\r\n]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*")/i,greedy:!0,inside:{id:{pattern:/^\w+/,greedy:!0,alias:"function"},escape:{pattern:/\\\$"|\$[$"]/,greedy:!0,alias:"symbol"},interpolation:{pattern:/\$(?:\w+|\{(?:[^{}]|\{[^{}]*\})*\})/,greedy:!0,inside:{punctuation:/^\$\{?|\}$/,expression:{pattern:/[\s\S]+/,inside:Prism.languages.scala}}},string:/[\s\S]+/}}}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala.function,delete Prism.languages.scala.constant},6966:function(){Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}},4793:function(){Prism.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ \t]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},Prism.languages.swift["string-literal"].forEach((function(e){e.inside.interpolation.inside=Prism.languages.swift}))},83:function(){!function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",i=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function s(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return"(?:"+i+"|"+o+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:s(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:s(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:s(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:s(o),lookbehind:!0,greedy:!0},number:{pattern:s(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism)},8848:function(e,t,n){var r=function(e){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,r={},i={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof o?new o(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=p.reach);S+=k.value.length,k=k.next){var E=k.value;if(t.length>e.length)return;if(!(E instanceof o)){var O,_=1;if(b){if(!(O=s(w,S,e,y))||O.index>=e.length)break;var A=O.index,C=O.index+O[0].length,j=S;for(j+=k.value.length;A>=j;)j+=(k=k.next).value.length;if(S=j-=k.value.length,k.value instanceof o)continue;for(var P=k;P!==t.tail&&(jp.reach&&(p.reach=N);var $=k.prev;if(I&&($=c(t,$,I),S+=I.length),u(t,$,_),k=c(t,$,new o(d,g?i.tokenize(T,g):T,v,T)),R&&c(t,k,R),_>1){var L={cause:d+","+h,reach:N};a(e,t,n,k.prev,S,L),p&&L.reach>p.reach&&(p.reach=L.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function c(e,t,n){var r=t.next,i={value:n,prev:t,next:r};return t.next=i,r.prev=i,e.length++,i}function u(e,t,n){for(var r=t.next,i=0;i"+o.content+""},!e.document)return e.addEventListener?(i.disableWorkerMessageHandler||e.addEventListener("message",(function(t){var n=JSON.parse(t.data),r=n.language,o=n.code,s=n.immediateClose;e.postMessage(i.highlight(o,i.languages[r],r)),s&&e.close()}),!1),i):i;var p=i.util.currentScript();function d(){i.manual||i.highlightAll()}if(p&&(i.filename=p.src,p.hasAttribute("data-manual")&&(i.manual=!0)),!i.manual){var f=document.readyState;"loading"===f||"interactive"===f&&p&&p.defer?document.addEventListener("DOMContentLoaded",d):window.requestAnimationFrame?window.requestAnimationFrame(d):window.setTimeout(d,16)}return i}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=r),void 0!==n.g&&(n.g.Prism=r),r.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},r.languages.markup.tag.inside["attr-value"].inside.entity=r.languages.markup.entity,r.languages.markup.doctype.inside["internal-subset"].inside=r.languages.markup,r.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(r.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:r.languages[t]},n.cdata=/^$/i;var i={"included-cdata":{pattern://i,inside:n}};i["language-"+t]={pattern:/[\s\S]+/,inside:r.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:i},r.languages.insertBefore("markup","cdata",o)}}),Object.defineProperty(r.languages.markup.tag,"addAttribute",{value:function(e,t){r.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:r.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),r.languages.html=r.languages.markup,r.languages.mathml=r.languages.markup,r.languages.svg=r.languages.markup,r.languages.xml=r.languages.extend("markup",{}),r.languages.ssml=r.languages.xml,r.languages.atom=r.languages.xml,r.languages.rss=r.languages.xml,function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(r),r.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},r.languages.javascript=r.languages.extend("clike",{"class-name":[r.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),r.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,r.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:r.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:r.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:r.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:r.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:r.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),r.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:r.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),r.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),r.languages.markup&&(r.languages.markup.tag.addInlined("script","javascript"),r.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),r.languages.js=r.languages.javascript,function(){if(void 0!==r&&"undefined"!=typeof document){Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector);var e={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},t="data-src-status",n="loading",i="loaded",o="pre[data-src]:not(["+t+'="'+i+'"]):not(['+t+'="'+n+'"])';r.hooks.add("before-highlightall",(function(e){e.selector+=", "+o})),r.hooks.add("before-sanity-check",(function(s){var a=s.element;if(a.matches(o)){s.code="",a.setAttribute(t,n);var l=a.appendChild(document.createElement("CODE"));l.textContent="Loading…";var c=a.getAttribute("data-src"),u=s.language;if("none"===u){var p=(/\.(\w+)$/.exec(c)||[,"none"])[1];u=e[p]||p}r.util.setLanguage(l,u),r.util.setLanguage(a,u);var d=r.plugins.autoloader;d&&d.loadLanguages(u),function(e,n,o){var s=new XMLHttpRequest;s.open("GET",e,!0),s.onreadystatechange=function(){4==s.readyState&&(s.status<400&&s.responseText?function(e){a.setAttribute(t,i);var n=function(e){var t=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||"");if(t){var n=Number(t[1]),r=t[2],i=t[3];return r?i?[n,Number(i)]:[n,void 0]:[n,n]}}(a.getAttribute("data-range"));if(n){var o=e.split(/\r\n?|\n/g),s=n[0],c=null==n[1]?o.length:n[1];s<0&&(s+=o.length),s=Math.max(0,Math.min(s-1,o.length)),c<0&&(c+=o.length),c=Math.max(0,Math.min(c,o.length)),e=o.slice(s,c).join("\n"),a.hasAttribute("data-start")||a.setAttribute("data-start",String(s+1))}l.textContent=e,r.highlightElement(l)}(s.responseText):s.status>=400?o("✖ Error "+s.status+" while fetching file: "+s.statusText):o("✖ Error: File does not exist or is empty"))},s.send(null)}(c,0,(function(e){a.setAttribute(t,"failed"),l.textContent=e}))}})),r.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(o),i=0;t=n[i++];)r.highlightElement(t)}};var s=!1;r.fileHighlight=function(){s||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),s=!0),r.plugins.fileHighlight.highlight.apply(this,arguments)}}}()},2694:function(e,t,n){"use strict";var r=n(6925);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,s){if(s!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},5556:function(e,t,n){e.exports=n(2694)()},6925:function(e){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:function(e,t,n){"use strict";var r=n(6540),i=n(194);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n
) to exclude the -only header row. + const dataRows = page.locator('table.table tr').filter({has: page.locator('td')}) + await expect(dataRows).toHaveCount(1) + + await dataRows.getByRole('button', {name: 'Delete'}).click() + await expect(dataRows).toHaveCount(0) + + // NOTE: the factory seeds the plaintext token as-is, but caldav tokens are + // stored bcrypt-hashed. We assert the row is gone in the UI rather than + // probing caldav with the seeded value. + }) }) From 7145440fe6694484fbd88ebc2b0db1934d647855 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:02:34 +0200 Subject: [PATCH 428/501] test(e2e): assert wrong password blocks email change --- .../tests/e2e/user/email-confirmation.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/tests/e2e/user/email-confirmation.spec.ts b/frontend/tests/e2e/user/email-confirmation.spec.ts index a19ff4b7f..7762376b0 100644 --- a/frontend/tests/e2e/user/email-confirmation.spec.ts +++ b/frontend/tests/e2e/user/email-confirmation.spec.ts @@ -163,3 +163,18 @@ test.describe('Email Confirmation', () => { await expect(page.locator('body')).toContainText(user.username) }) }) + +test.describe('Email change', () => { + test('rejects email change with wrong current password', async ({authenticatedPage: page}) => { + await page.goto('/user/settings/email-update') + await page.locator('#newEmail').fill('new@example.com') + await page.locator('#currentPasswordEmail').fill('WRONG_PASSWORD') + + const resp = page.waitForResponse(r => r.url().includes('/user/settings/email')) + await page.getByRole('button', {name: 'Save'}).last().click() + const r = await resp + expect(r.status()).toBeGreaterThanOrEqual(400) + + await expect(page.locator('.global-notification .vue-notification.error')).toBeVisible() + }) +}) From cf9d0a26ab364ce1b80aff0d1894e284d00cdcb6 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:05:37 +0200 Subject: [PATCH 429/501] test(e2e): cover sessions list with current marker --- .../tests/e2e/user/settings/sessions.spec.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 frontend/tests/e2e/user/settings/sessions.spec.ts diff --git a/frontend/tests/e2e/user/settings/sessions.spec.ts b/frontend/tests/e2e/user/settings/sessions.spec.ts new file mode 100644 index 000000000..222d06dbb --- /dev/null +++ b/frontend/tests/e2e/user/settings/sessions.spec.ts @@ -0,0 +1,23 @@ +import {test, expect} from '../../../support/fixtures' +import {SessionFactory} from '../../../factories/session' +import {gotoUserSettings} from '../../../support/userSettings' + +test.describe('Sessions', () => { + test('lists the current session and other sessions', async ({ + authenticatedPage: page, currentUser, + }) => { + // The auth fixture already created one session row (the login). + // Seed one additional session with truncate=false so we don't wipe it. + await SessionFactory.create(1, { + user_id: currentUser.id, + device_info: 'Firefox on Linux', + ip_address: '192.0.2.5', + }, false) + + await gotoUserSettings(page, 'sessions') + const rows = page.locator('table.table tbody tr') + await expect(rows).toHaveCount(2) + await expect(page.locator('.tag.is-primary')).toContainText('Current') + await expect(page.locator('tr', {hasText: 'Firefox on Linux'})).toContainText('192.0.2.5') + }) +}) From 76055b622bb59d38a6220786706e8f3b045278eb Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:06:44 +0200 Subject: [PATCH 430/501] test(e2e): assert session delete breaks refresh --- .../tests/e2e/user/settings/sessions.spec.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/frontend/tests/e2e/user/settings/sessions.spec.ts b/frontend/tests/e2e/user/settings/sessions.spec.ts index 222d06dbb..841b78a4b 100644 --- a/frontend/tests/e2e/user/settings/sessions.spec.ts +++ b/frontend/tests/e2e/user/settings/sessions.spec.ts @@ -1,5 +1,5 @@ import {test, expect} from '../../../support/fixtures' -import {SessionFactory} from '../../../factories/session' +import {SessionFactory, hashSessionToken} from '../../../factories/session' import {gotoUserSettings} from '../../../support/userSettings' test.describe('Sessions', () => { @@ -20,4 +20,29 @@ test.describe('Sessions', () => { await expect(page.locator('.tag.is-primary')).toContainText('Current') await expect(page.locator('tr', {hasText: 'Firefox on Linux'})).toContainText('192.0.2.5') }) + + test('revoking a session breaks its refresh token', async ({ + authenticatedPage: page, currentUser, apiContext, + }) => { + const rawToken = 'fixed-refresh-token-for-test-12345678901234567890' + await SessionFactory.create(1, { + user_id: currentUser.id, + token_hash: hashSessionToken(rawToken), + ip_address: '192.0.2.5', + device_info: 'Firefox on Linux', + }, false) + + await gotoUserSettings(page, 'sessions') + await page.locator('tr', {hasText: /192\.0\.2\.5/}) + .getByRole('button', {name: 'Delete'}).click() + await page.locator('dialog[open] .modal-content .actions .button').filter({hasText: 'Do it!'}).click() + await expect(page.locator('table.table tbody tr')).toHaveCount(1) + + // After revoke, the refresh request must fail. Refresh tokens live in the + // vikunja_refresh_token cookie, not as a Bearer credential. + const after = await apiContext.post('user/token/refresh', { + headers: {Cookie: `vikunja_refresh_token=${rawToken}`}, + }) + expect(after.status()).toBe(401) + }) }) From 0902c009f6521b1e568d3633cab53b65400b3b4f Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:07:36 +0200 Subject: [PATCH 431/501] test(e2e): assert current session has no delete control --- frontend/tests/e2e/user/settings/sessions.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/tests/e2e/user/settings/sessions.spec.ts b/frontend/tests/e2e/user/settings/sessions.spec.ts index 841b78a4b..5b4305a9b 100644 --- a/frontend/tests/e2e/user/settings/sessions.spec.ts +++ b/frontend/tests/e2e/user/settings/sessions.spec.ts @@ -45,4 +45,10 @@ test.describe('Sessions', () => { }) expect(after.status()).toBe(401) }) + + test('current session cannot be deleted from the UI', async ({authenticatedPage: page}) => { + await gotoUserSettings(page, 'sessions') + const currentRow = page.locator('tr', {has: page.locator('.tag.is-primary')}) + await expect(currentRow.getByRole('button', {name: 'Delete'})).toHaveCount(0) + }) }) From 2a5e4f2b840ca0658550d9851bf124289018da24 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:10:21 +0200 Subject: [PATCH 432/501] test(e2e): cover account deletion request flow --- .../tests/e2e/user/settings/deletion.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 frontend/tests/e2e/user/settings/deletion.spec.ts diff --git a/frontend/tests/e2e/user/settings/deletion.spec.ts b/frontend/tests/e2e/user/settings/deletion.spec.ts new file mode 100644 index 000000000..2a66231f5 --- /dev/null +++ b/frontend/tests/e2e/user/settings/deletion.spec.ts @@ -0,0 +1,22 @@ +import {test, expect} from '../../../support/fixtures' +import {gotoUserSettings} from '../../../support/userSettings' +import {TEST_PASSWORD} from '../../../support/constants' + +test.describe('Account deletion', () => { + test('blocks deletion request with no password', async ({authenticatedPage: page}) => { + await gotoUserSettings(page, 'deletion') + await page.locator('.card .button.is-danger').click() + await expect(page.locator('.help.is-danger')).toContainText(/password/i) + }) + + test('schedules deletion with correct password', async ({authenticatedPage: page}) => { + await gotoUserSettings(page, 'deletion') + await page.locator('#currentPasswordAccountDelete').fill(TEST_PASSWORD) + + const resp = page.waitForResponse(r => r.url().includes('/user/deletion/request')) + await page.locator('.card .button.is-danger').click() + const r = await resp + expect(r.ok()).toBe(true) + await expect(page.locator('.global-notification .vue-notification.success')).toBeVisible() + }) +}) From a9f8fbaba8e3beef9d1498cce9c2b13788f83213 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:11:38 +0200 Subject: [PATCH 433/501] test(e2e): cover scheduled deletion cancel flow --- .../tests/e2e/user/settings/deletion.spec.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/frontend/tests/e2e/user/settings/deletion.spec.ts b/frontend/tests/e2e/user/settings/deletion.spec.ts index 2a66231f5..6bcf678f8 100644 --- a/frontend/tests/e2e/user/settings/deletion.spec.ts +++ b/frontend/tests/e2e/user/settings/deletion.spec.ts @@ -1,6 +1,7 @@ import {test, expect} from '../../../support/fixtures' import {gotoUserSettings} from '../../../support/userSettings' import {TEST_PASSWORD} from '../../../support/constants' +import {TokenFactory} from '../../../factories/token' test.describe('Account deletion', () => { test('blocks deletion request with no password', async ({authenticatedPage: page}) => { @@ -19,4 +20,36 @@ test.describe('Account deletion', () => { expect(r.ok()).toBe(true) await expect(page.locator('.global-notification .vue-notification.success')).toBeVisible() }) + + test('cancels a scheduled deletion', async ({ + authenticatedPage: page, currentUser, userToken, apiContext, + }) => { + const deletionToken = 'fixed-account-deletion-token-1234567890123456' + // kind=3 is TokenAccountDeletion (see pkg/user/token.go) + await TokenFactory.create(1, { + user_id: currentUser.id, + kind: 3, + token: deletionToken, + }, false) + + // Confirm the deletion via API — this is the write that sets deletion_scheduled_at. + const confirm = await apiContext.post('user/deletion/confirm', { + headers: {Authorization: `Bearer ${userToken}`}, + data: {token: deletionToken}, + }) + expect(confirm.ok()).toBe(true) + + await gotoUserSettings(page, 'deletion') + // Scheduled-state copy: "We will delete your Vikunja account at ..." + await expect(page.locator('.card')).toContainText(/we will delete your Vikunja account/i) + + await page.locator('#currentPasswordAccountDelete').fill(TEST_PASSWORD) + const cancel = page.waitForResponse(r => r.url().includes('/user/deletion/cancel')) + await page.getByRole('button', {name: /cancel the deletion/i}).click() + await cancel + + await expect(page.locator('.global-notification .vue-notification.success')).toBeVisible() + // And the non-scheduled branch (the "Delete account" form) reappears. + await expect(page.locator('.card .button.is-danger')).toBeVisible() + }) }) From 8bcdc314b1f250daf16a04863046c5ce83e42f0c Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:13:25 +0200 Subject: [PATCH 434/501] test(e2e): cover data export request flow --- .../e2e/user/settings/data-export.spec.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/tests/e2e/user/settings/data-export.spec.ts diff --git a/frontend/tests/e2e/user/settings/data-export.spec.ts b/frontend/tests/e2e/user/settings/data-export.spec.ts new file mode 100644 index 000000000..31fda6c4f --- /dev/null +++ b/frontend/tests/e2e/user/settings/data-export.spec.ts @@ -0,0 +1,27 @@ +import {test, expect} from '../../../support/fixtures' +import {gotoUserSettings} from '../../../support/userSettings' +import {TEST_PASSWORD} from '../../../support/constants' + +test.describe('Data export', () => { + test('requests an export with correct password', async ({authenticatedPage: page}) => { + await gotoUserSettings(page, 'data-export') + await page.locator('#currentPasswordDataExport').fill(TEST_PASSWORD) + + const resp = page.waitForResponse(r => r.url().includes('/user/export/request')) + await page.getByRole('button', {name: /request/i}).click() + const r = await resp + expect(r.ok()).toBe(true) + await expect(page.locator('.global-notification .vue-notification.success')).toBeVisible() + }) + + test('rejects export with wrong password', async ({authenticatedPage: page}) => { + await gotoUserSettings(page, 'data-export') + await page.locator('#currentPasswordDataExport').fill('WRONG') + + const resp = page.waitForResponse(r => r.url().includes('/user/export/request')) + await page.getByRole('button', {name: /request/i}).click() + const r = await resp + expect(r.ok()).toBe(false) + await expect(page.locator('.global-notification .vue-notification.error')).toBeVisible() + }) +}) From 2f2aafadfdd226281603195016a2cc41ca534a29 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:17:55 +0200 Subject: [PATCH 435/501] test(e2e): validate webhook target url --- frontend/tests/e2e/project/webhooks.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 frontend/tests/e2e/project/webhooks.spec.ts diff --git a/frontend/tests/e2e/project/webhooks.spec.ts b/frontend/tests/e2e/project/webhooks.spec.ts new file mode 100644 index 000000000..faf72fb41 --- /dev/null +++ b/frontend/tests/e2e/project/webhooks.spec.ts @@ -0,0 +1,16 @@ +import {test, expect} from '../../support/fixtures' +import {ProjectFactory} from '../../factories/project' + +test.describe('Project webhooks', () => { + test.beforeEach(async ({authenticatedPage, currentUser}) => { + await ProjectFactory.create(1, {id: 1, owner_id: currentUser.id}, false) + }) + + test('validates the target URL', async ({authenticatedPage: page}) => { + await page.goto('/projects/1/settings/webhooks') + await page.waitForLoadState('networkidle') + await page.locator('#targetUrl').fill('not-a-url') + await page.locator('#targetUrl').blur() + await expect(page.locator('.help.is-danger')).toContainText(/valid URL/i) + }) +}) From 5a931498491f135f2143561f1285a9586ef5a893 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:18:41 +0200 Subject: [PATCH 436/501] test(e2e): require at least one webhook event --- frontend/tests/e2e/project/webhooks.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/tests/e2e/project/webhooks.spec.ts b/frontend/tests/e2e/project/webhooks.spec.ts index faf72fb41..961e42571 100644 --- a/frontend/tests/e2e/project/webhooks.spec.ts +++ b/frontend/tests/e2e/project/webhooks.spec.ts @@ -13,4 +13,12 @@ test.describe('Project webhooks', () => { await page.locator('#targetUrl').blur() await expect(page.locator('.help.is-danger')).toContainText(/valid URL/i) }) + + test('requires at least one event', async ({authenticatedPage: page}) => { + await page.goto('/projects/1/settings/webhooks') + await page.waitForLoadState('networkidle') + await page.locator('#targetUrl').fill('https://example.com/hook') + await page.getByRole('button', {name: /create webhook/i}).click() + await expect(page.locator('.help.is-danger')).toContainText(/at least one event/i) + }) }) From 425889b8793598e7fd278092d20d47825d7d5221 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:21:55 +0200 Subject: [PATCH 437/501] test(e2e): create and delete a webhook --- frontend/tests/e2e/project/webhooks.spec.ts | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frontend/tests/e2e/project/webhooks.spec.ts b/frontend/tests/e2e/project/webhooks.spec.ts index 961e42571..857e87d78 100644 --- a/frontend/tests/e2e/project/webhooks.spec.ts +++ b/frontend/tests/e2e/project/webhooks.spec.ts @@ -21,4 +21,31 @@ test.describe('Project webhooks', () => { await page.getByRole('button', {name: /create webhook/i}).click() await expect(page.locator('.help.is-danger')).toContainText(/at least one event/i) }) + + test('creates and deletes a webhook', async ({authenticatedPage: page}) => { + await page.goto('/projects/1/settings/webhooks') + await page.waitForLoadState('networkidle') + + await page.locator('#targetUrl').fill('https://example.com/hook') + await page.locator('.available-events-check', {hasText: 'task.created'}) + .locator('.base-checkbox__label').click() + + const created = page.waitForResponse(r => + r.url().includes('/projects/1/webhooks') && r.request().method() === 'PUT', + ) + await page.getByRole('button', {name: /create webhook/i}).click() + await created + + const row = page.locator('table.table tbody tr', {hasText: 'example.com/hook'}) + await expect(row).toBeVisible() + + const deleted = page.waitForResponse(r => + r.url().match(/\/projects\/1\/webhooks\/\d+/) !== null && r.request().method() === 'DELETE', + ) + await row.locator('.button.is-danger').click() + await page.locator('dialog[open] .modal-content .actions .button').filter({hasText: 'Do it!'}).click() + await deleted + + await expect(row).toHaveCount(0) + }) }) From db634093e0746bc0168ca975c0314a7cb40efbd9 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:22:57 +0200 Subject: [PATCH 438/501] test(e2e): drop unused authenticatedPage from webhooks beforeEach --- frontend/tests/e2e/project/webhooks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tests/e2e/project/webhooks.spec.ts b/frontend/tests/e2e/project/webhooks.spec.ts index 857e87d78..6d1c1348b 100644 --- a/frontend/tests/e2e/project/webhooks.spec.ts +++ b/frontend/tests/e2e/project/webhooks.spec.ts @@ -2,7 +2,7 @@ import {test, expect} from '../../support/fixtures' import {ProjectFactory} from '../../factories/project' test.describe('Project webhooks', () => { - test.beforeEach(async ({authenticatedPage, currentUser}) => { + test.beforeEach(async ({currentUser}) => { await ProjectFactory.create(1, {id: 1, owner_id: currentUser.id}, false) }) From 05432d399322141804564f5d220059873c08a4b8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:33:26 +0200 Subject: [PATCH 439/501] test(e2e): cover attachment deletion --- frontend/tests/e2e/task/task.spec.ts | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/frontend/tests/e2e/task/task.spec.ts b/frontend/tests/e2e/task/task.spec.ts index c8b55e81e..af850130a 100644 --- a/frontend/tests/e2e/task/task.spec.ts +++ b/frontend/tests/e2e/task/task.spec.ts @@ -959,6 +959,35 @@ test.describe('Task', () => { await expect(page.locator('.bucket .task .footer .icon svg.fa-paperclip')).toBeVisible() }) + test('Can delete an attachment', async ({authenticatedPage: page}) => { + const tasks = await TaskFactory.create(1, { + id: 1, + project_id: projects[0].id, + }) + await page.goto(`/tasks/${tasks[0].id}`) + + await uploadAttachmentAndVerify(page, tasks[0].id) + + // The delete control is the third `.attachment-info-meta-button` + // (download, copy URL, delete) inside the attachment row. Attachments.vue + // requests `trash-alt` but FontAwesome renders it as `trash-can`. + const deleteButton = page.locator( + '.attachments .attachments .files button.attachment .attachment-info-meta-button:has(svg[data-icon="trash-can"])', + ).first() + await expect(deleteButton).toBeVisible() + + const deleted = page.waitForResponse(r => + /\/tasks\/\d+\/attachments\/\d+/.test(r.url()) && r.request().method() === 'DELETE', + ) + await deleteButton.click() + + // Confirm in the modal ("Do it!"). + await page.locator('dialog[open] .modal-content .actions .button').filter({hasText: 'Do it!'}).click() + await deleted + + await expect(page.locator('.attachments .attachments .files button.attachment')).toHaveCount(0) + }) + test('Can check items off a checklist', async ({authenticatedPage: page}) => { const tasks = await TaskFactory.create(1, { id: 1, From f2eee5d8a133609e5715df1855c43c71f3276820 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:34:21 +0200 Subject: [PATCH 440/501] test(e2e): assert readers cannot delete attachments --- frontend/tests/e2e/task/task.spec.ts | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/frontend/tests/e2e/task/task.spec.ts b/frontend/tests/e2e/task/task.spec.ts index af850130a..9ad9d7bb5 100644 --- a/frontend/tests/e2e/task/task.spec.ts +++ b/frontend/tests/e2e/task/task.spec.ts @@ -18,6 +18,7 @@ import {TaskReminderFactory} from '../../factories/task_reminders' import {createDefaultViews} from '../project/prepareProjects' import {TaskBucketFactory} from '../../factories/task_buckets' import {pasteFile} from '../../support/commands' +import {login} from '../../support/authenticateUser' import type {Page} from '@playwright/test' import {readFileSync} from 'fs' import {join, dirname} from 'path' @@ -988,6 +989,65 @@ test.describe('Task', () => { await expect(page.locator('.attachments .attachments .files button.attachment')).toHaveCount(0) }) + test('read-only shared user cannot delete attachments', async ({authenticatedPage: page, apiContext, currentUser}) => { + // Second user who will own the project and upload the attachment. + const [owner] = await UserFactory.create(1, { + id: 200, + }, false) + + // Project owned by the owner, shared read-only with currentUser. + const [sharedProject] = await ProjectFactory.create(1, { + id: 500, + title: 'Read-Only Shared Project', + owner_id: owner.id, + }, false) + + const [sharedTask] = await TaskFactory.create(1, { + id: 500, + title: 'Shared task with attachment', + project_id: sharedProject.id, + created_by_id: owner.id, + }, false) + + await UserProjectFactory.create(1, { + id: 500, + project_id: sharedProject.id, + user_id: currentUser.id, + permission: 0, + }, false) + + // Upload an attachment as the owner via the real API so the files + // table gets populated correctly. + const {token: ownerToken} = await login(null, apiContext, owner) + const filePath = join(__dirname, '../../fixtures/image.jpg') + const fileBuffer = readFileSync(filePath) + const uploadResp = await apiContext.put(`tasks/${sharedTask.id}/attachments`, { + multipart: { + files: { + name: 'image.jpg', + mimeType: 'image/jpeg', + buffer: fileBuffer, + }, + }, + headers: { + 'Authorization': `Bearer ${ownerToken}`, + }, + }) + expect(uploadResp.ok()).toBe(true) + + // currentUser is already authenticated in the page via the fixture. + await page.goto(`/tasks/${sharedTask.id}`) + + // The attachment must be visible to the reader. + await expect(page.locator('.attachments .attachments .files button.attachment')).toBeVisible() + + // The delete control renders only when editEnabled is true + // (see Attachments.vue). A read-only viewer should not see it. + await expect(page.locator( + '.attachments .attachments .files button.attachment .attachment-info-meta-button:has(svg[data-icon="trash-can"])', + )).toHaveCount(0) + }) + test('Can check items off a checklist', async ({authenticatedPage: page}) => { const tasks = await TaskFactory.create(1, { id: 1, From c93f644363696682075ba1edd79c490f031c0367 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:39:29 +0200 Subject: [PATCH 441/501] test(e2e): cover recurrence preset buttons --- frontend/tests/e2e/task/recurrence.spec.ts | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 frontend/tests/e2e/task/recurrence.spec.ts diff --git a/frontend/tests/e2e/task/recurrence.spec.ts b/frontend/tests/e2e/task/recurrence.spec.ts new file mode 100644 index 000000000..27161df3e --- /dev/null +++ b/frontend/tests/e2e/task/recurrence.spec.ts @@ -0,0 +1,29 @@ +import {test, expect} from '../../support/fixtures' +import {ProjectFactory} from '../../factories/project' +import {TaskFactory} from '../../factories/task' + +test.describe('Task recurrence', () => { + test.beforeEach(async ({authenticatedPage}) => { + await ProjectFactory.create(1, {id: 1}) + }) + + test('sets repeat-every-day via preset button', async ({authenticatedPage: page}) => { + const [task] = await TaskFactory.create(1, { + id: 1, + project_id: 1, + due_date: new Date(Date.now() + 86_400_000).toISOString(), + }, false) + await page.goto(`/tasks/${task.id}`) + + // Reveal the RepeatAfter component (hidden until the user activates it) + await page.getByRole('button', {name: 'Set Repeating Interval'}).click() + + const save = page.waitForResponse(r => + r.url().includes(`/tasks/${task.id}`) && r.request().method() === 'POST', + ) + await page.getByRole('button', {name: 'Every Day'}).click() + const r = await save + const body = r.request().postDataJSON() + expect(body.repeat_after).toBe(86400) + }) +}) From 637d810ff75c23acca87b751e378842637cd9655 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:40:17 +0200 Subject: [PATCH 442/501] test(e2e): assert recurring task regenerates on complete --- frontend/tests/e2e/task/recurrence.spec.ts | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/frontend/tests/e2e/task/recurrence.spec.ts b/frontend/tests/e2e/task/recurrence.spec.ts index 27161df3e..46201bba8 100644 --- a/frontend/tests/e2e/task/recurrence.spec.ts +++ b/frontend/tests/e2e/task/recurrence.spec.ts @@ -26,4 +26,36 @@ test.describe('Task recurrence', () => { const body = r.request().postDataJSON() expect(body.repeat_after).toBe(86400) }) + + test('completing a recurring task reopens with advanced due date', async ({ + authenticatedPage: page, apiContext, userToken, + }) => { + const originalDue = new Date(Date.now() + 86_400_000) + const [task] = await TaskFactory.create(1, { + id: 1, + project_id: 1, + due_date: originalDue.toISOString(), + repeat_after: 86400, + }, false) + + await page.goto(`/tasks/${task.id}`) + + const completed = page.waitForResponse(r => + r.url().includes(`/tasks/${task.id}`) && r.request().method() === 'POST', + ) + await page.locator('.task-view .action-buttons .button').filter({hasText: 'Mark task done!'}).click() + await completed + + // Fetch fresh state from the API to verify the backend regenerated the task. + const resp = await apiContext.get(`tasks/${task.id}`, { + headers: {Authorization: `Bearer ${userToken}`}, + }) + expect(resp.ok()).toBe(true) + const refreshed = await resp.json() + expect(refreshed.done).toBe(false) + const newDue = new Date(refreshed.due_date).getTime() + // addRepeatIntervalToTime: when the original due date is still in the + // future, the backend advances it by exactly one interval (86400s here). + expect(newDue - originalDue.getTime()).toBeCloseTo(86_400_000, -3) + }) }) From 37d7f90acf1eaaef0bec108a0657a85adcd6280b Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:41:08 +0200 Subject: [PATCH 443/501] test(e2e): cover monthly repeat mode UI --- frontend/tests/e2e/task/recurrence.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/frontend/tests/e2e/task/recurrence.spec.ts b/frontend/tests/e2e/task/recurrence.spec.ts index 46201bba8..d341cc430 100644 --- a/frontend/tests/e2e/task/recurrence.spec.ts +++ b/frontend/tests/e2e/task/recurrence.spec.ts @@ -58,4 +58,20 @@ test.describe('Task recurrence', () => { // future, the backend advances it by exactly one interval (86400s here). expect(newDue - originalDue.getTime()).toBeCloseTo(86_400_000, -3) }) + + test('monthly repeat mode hides the amount field', async ({authenticatedPage: page}) => { + const [task] = await TaskFactory.create(1, {id: 1, project_id: 1}, false) + await page.goto(`/tasks/${task.id}`) + + // Reveal the RepeatAfter component (hidden until the user activates it) + await page.getByRole('button', {name: 'Set Repeating Interval'}).click() + + await expect(page.locator('#repeatMode')).toBeVisible() + // Amount input is visible in the default repeat mode + await expect(page.locator('input[placeholder*="amount" i]')).toHaveCount(1) + + await page.locator('#repeatMode').selectOption({label: 'Monthly'}) + + await expect(page.locator('input[placeholder*="amount" i]')).toHaveCount(0) + }) }) From 268c5daf8bc591ad6e27f06da9ba5d47b8333fb7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:41:36 +0200 Subject: [PATCH 444/501] test(e2e): drop unused authenticatedPage from recurrence beforeEach --- frontend/tests/e2e/task/recurrence.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tests/e2e/task/recurrence.spec.ts b/frontend/tests/e2e/task/recurrence.spec.ts index d341cc430..76101c250 100644 --- a/frontend/tests/e2e/task/recurrence.spec.ts +++ b/frontend/tests/e2e/task/recurrence.spec.ts @@ -3,7 +3,7 @@ import {ProjectFactory} from '../../factories/project' import {TaskFactory} from '../../factories/task' test.describe('Task recurrence', () => { - test.beforeEach(async ({authenticatedPage}) => { + test.beforeEach(async () => { await ProjectFactory.create(1, {id: 1}) }) From 01b71577d7fb538bab8318996dfac5b549c0298b Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:43:26 +0200 Subject: [PATCH 445/501] test(e2e): add TeamProjectFactory --- frontend/tests/factories/team_project.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/tests/factories/team_project.ts diff --git a/frontend/tests/factories/team_project.ts b/frontend/tests/factories/team_project.ts new file mode 100644 index 000000000..f9e635e7b --- /dev/null +++ b/frontend/tests/factories/team_project.ts @@ -0,0 +1,18 @@ +import {Factory} from '../support/factory' + +export class TeamProjectFactory extends Factory { + static table = 'team_projects' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + team_id: 1, + project_id: 1, + permission: 0, + created: now.toISOString(), + updated: now.toISOString(), + } + } +} From be225fd4d3125df2339f72b25ef39437fdbf7200 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:45:11 +0200 Subject: [PATCH 446/501] test(e2e): cover team READ permission boundary --- frontend/tests/e2e/sharing/team.spec.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/frontend/tests/e2e/sharing/team.spec.ts b/frontend/tests/e2e/sharing/team.spec.ts index 19bfbcee8..cc20648d6 100644 --- a/frontend/tests/e2e/sharing/team.spec.ts +++ b/frontend/tests/e2e/sharing/team.spec.ts @@ -1,7 +1,10 @@ import {test, expect} from '../../support/fixtures' import {TeamFactory} from '../../factories/team' import {TeamMemberFactory} from '../../factories/team_member' +import {TeamProjectFactory} from '../../factories/team_project' import {UserFactory} from '../../factories/user' +import {createProjects} from '../project/prepareProjects' +import {login, setupApiUrl} from '../../support/authenticateUser' test.describe('Team', () => { test('Creates a new team', async ({authenticatedPage: page}) => { @@ -100,3 +103,25 @@ test.describe('Team', () => { await expect(page.locator('.global-notification')).toContainText('Success') }) }) + +test.describe('Team permission tiers on shared projects', () => { + // These tests log in as the second user (the team member), so we can't use + // the `authenticatedPage` fixture which auto-logs in as user 1. + test.beforeEach(async ({page}) => { + await setupApiUrl(page) + }) + + test('READ: team member cannot add tasks on a shared project', async ({page, apiContext}) => { + const [, member] = await UserFactory.create(2) + await createProjects(1) + await TeamFactory.create(1, {id: 1, created_by_id: 1}, false) + await TeamMemberFactory.create(1, {team_id: 1, user_id: member.id, admin: false}, false) + await TeamProjectFactory.create(1, {team_id: 1, project_id: 1, permission: 0}, false) + + await login(page, apiContext, member) + await page.goto('/projects/1/1') + + await expect(page.locator('.project-title')).toContainText('First Project') + await expect(page.locator('input.input[placeholder="Add a task…"]')).not.toBeVisible() + }) +}) From f20267164fd96b981a269b385437c73aff8873c6 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:45:54 +0200 Subject: [PATCH 447/501] test(e2e): cover team READ_WRITE permission --- frontend/tests/e2e/sharing/team.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/tests/e2e/sharing/team.spec.ts b/frontend/tests/e2e/sharing/team.spec.ts index cc20648d6..7a4d9906a 100644 --- a/frontend/tests/e2e/sharing/team.spec.ts +++ b/frontend/tests/e2e/sharing/team.spec.ts @@ -124,4 +124,18 @@ test.describe('Team permission tiers on shared projects', () => { await expect(page.locator('.project-title')).toContainText('First Project') await expect(page.locator('input.input[placeholder="Add a task…"]')).not.toBeVisible() }) + + test('READ_WRITE: team member can add tasks on a shared project', async ({page, apiContext}) => { + const [, member] = await UserFactory.create(2) + await createProjects(1) + await TeamFactory.create(1, {id: 1, created_by_id: 1}, false) + await TeamMemberFactory.create(1, {team_id: 1, user_id: member.id, admin: false}, false) + await TeamProjectFactory.create(1, {team_id: 1, project_id: 1, permission: 1}, false) + + await login(page, apiContext, member) + await page.goto('/projects/1/1') + + await expect(page.locator('.project-title')).toContainText('First Project') + await expect(page.locator('.task-add textarea')).toBeVisible() + }) }) From 19d3b9c4bbed8fa6cab3778eae8b5456729e96e1 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:48:11 +0200 Subject: [PATCH 448/501] test(e2e): cover team share revocation --- frontend/tests/e2e/sharing/team.spec.ts | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/frontend/tests/e2e/sharing/team.spec.ts b/frontend/tests/e2e/sharing/team.spec.ts index 7a4d9906a..f918b101b 100644 --- a/frontend/tests/e2e/sharing/team.spec.ts +++ b/frontend/tests/e2e/sharing/team.spec.ts @@ -138,4 +138,45 @@ test.describe('Team permission tiers on shared projects', () => { await expect(page.locator('.project-title')).toContainText('First Project') await expect(page.locator('.task-add textarea')).toBeVisible() }) + + test('owner can revoke team share and member loses access', async ({page, apiContext}) => { + const [owner, member] = await UserFactory.create(2) + await createProjects(1) + const [team] = await TeamFactory.create(1, {id: 1, name: 'Shared Team', created_by_id: owner.id}, false) + await TeamMemberFactory.create(1, {team_id: team.id, user_id: member.id, admin: false}, false) + await TeamProjectFactory.create(1, {team_id: team.id, project_id: 1, permission: 1}, false) + + // Sanity check: member can see the project before revocation. + await login(page, apiContext, member) + await page.goto('/projects/1/1') + await expect(page.locator('.project-title')).toContainText('First Project') + + // Owner opens the share settings and removes the team. + await login(page, apiContext, owner) + await page.goto('/projects/1/settings/share') + + const teamRow = page.locator('table.table tbody tr').filter({hasText: team.name}) + await expect(teamRow).toBeVisible() + await teamRow.locator('.button.is-danger, button.is-danger').first().click() + + const deleteRequest = page.waitForResponse(r => + r.url().includes('/projects/1/teams/') && r.request().method() === 'DELETE', + ) + await page.locator('dialog[open] .modal-content .actions .button') + .filter({hasText: 'Do it!'}).click() + await deleteRequest + + await expect(teamRow).toHaveCount(0) + + // Member can no longer open the project: the API returns a permission + // error and the frontend never renders the project title. + await login(page, apiContext, member) + const projectResponse = page.waitForResponse(r => + r.url().endsWith('/projects/1') && r.request().method() === 'GET', + ) + await page.goto('/projects/1/1') + const resp = await projectResponse + expect(resp.status()).toBeGreaterThanOrEqual(400) + await expect(page.locator('.project-title').filter({hasText: 'First Project'})).toHaveCount(0) + }) }) From 17e0dde7d33f14b0cda0fbc004bae358df49b9c5 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:55:18 +0200 Subject: [PATCH 449/501] test(e2e): cover link share password protection --- frontend/tests/e2e/sharing/linkShare.spec.ts | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/frontend/tests/e2e/sharing/linkShare.spec.ts b/frontend/tests/e2e/sharing/linkShare.spec.ts index 23c907da6..fd9885c98 100644 --- a/frontend/tests/e2e/sharing/linkShare.spec.ts +++ b/frontend/tests/e2e/sharing/linkShare.spec.ts @@ -4,6 +4,7 @@ import {TaskFactory} from '../../factories/task' import {UserFactory} from '../../factories/user' import {createProjects} from '../project/prepareProjects' import {login, setupApiUrl} from '../../support/authenticateUser' +import {TEST_PASSWORD, TEST_PASSWORD_HASH} from '../../support/constants' async function prepareLinkShare() { await UserFactory.create() @@ -110,3 +111,70 @@ test.describe('Link shares', () => { await expect(page).toHaveURL(`/projects/${project.id}/1#share-auth-token=${share.hash}`) }) }) + +test.describe('Link share: password protection', () => { + test.beforeEach(async ({page}) => { + await setupApiUrl(page) + }) + + test('password-protected share rejects wrong password', async ({page}) => { + await UserFactory.create(1) + const projects = await createProjects() + const [share] = await LinkShareFactory.create(1, { + project_id: projects[0].id, + sharing_type: 2, + password: TEST_PASSWORD_HASH, + permission: 0, + }) + + await page.goto(`/share/${share.hash}/auth`) + + // The auth form renders only once the backend returns code 13001, so wait + // for it before trying to type into the password field. + const passwordInput = page.locator('input#linkSharePassword') + await expect(passwordInput).toBeVisible() + + await passwordInput.fill('wrong-password') + // Wait for the auth POST to complete so we can assert the negative + // outcome without racing the UI. + const authRejected = page.waitForResponse(r => + r.url().includes(`/shares/${share.hash}/auth`) && r.request().method() === 'POST', + ) + await page.locator('.button').filter({hasText: 'Login'}).click() + const resp = await authRejected + expect(resp.status()).toBeGreaterThanOrEqual(400) + + // The user must not be redirected into the shared project view, and + // the route stays on the link-share auth URL. + await expect(page).toHaveURL(new RegExp(`/share/${share.hash}/auth`)) + // No project-title heading renders while we're still on the auth route. + await expect(page.locator('h1.title')).toHaveCount(0) + }) + + test('password-protected share accepts correct password', async ({page}) => { + await UserFactory.create(1) + const projects = await createProjects() + const tasks = await TaskFactory.create(3, { + project_id: projects[0].id, + }) + const [share] = await LinkShareFactory.create(1, { + project_id: projects[0].id, + sharing_type: 2, + password: TEST_PASSWORD_HASH, + permission: 0, + }) + + await page.goto(`/share/${share.hash}/auth`) + + const passwordInput = page.locator('input#linkSharePassword') + await expect(passwordInput).toBeVisible() + + await passwordInput.fill(TEST_PASSWORD) + await page.locator('.button').filter({hasText: 'Login'}).click() + + await expect(page.locator('h1.title')).toContainText(projects[0].title) + await expect(page.locator('.tasks')).toContainText(tasks[0].title) + await expect(page).toHaveURL(`/projects/${projects[0].id}/1#share-auth-token=${share.hash}`) + }) +}) + From c3b86b210282a28c1763618d609c933e8bc73ab3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 11:56:39 +0200 Subject: [PATCH 450/501] test(e2e): cover link share permission tiers --- frontend/tests/e2e/sharing/linkShare.spec.ts | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/frontend/tests/e2e/sharing/linkShare.spec.ts b/frontend/tests/e2e/sharing/linkShare.spec.ts index fd9885c98..8d1d58ef9 100644 --- a/frontend/tests/e2e/sharing/linkShare.spec.ts +++ b/frontend/tests/e2e/sharing/linkShare.spec.ts @@ -178,3 +178,48 @@ test.describe('Link share: password protection', () => { }) }) +test.describe('Link share: permission tiers', () => { + test.beforeEach(async ({page}) => { + await setupApiUrl(page) + }) + + test('READ link share hides add-task', async ({page}) => { + await UserFactory.create(1) + const projects = await createProjects() + await TaskFactory.create(3, { + project_id: projects[0].id, + }) + const [share] = await LinkShareFactory.create(1, { + project_id: projects[0].id, + permission: 0, + }) + + await page.goto(`/share/${share.hash}/auth`) + + // Wait for the project view to actually render so the assertion isn't + // vacuously true during the loading shell. + await expect(page.locator('h1.title')).toContainText(projects[0].title) + await expect(page).toHaveURL(`/projects/${projects[0].id}/1#share-auth-token=${share.hash}`) + + await expect(page.locator('.input[placeholder="Add a task…"]')).toHaveCount(0) + }) + + test('READ_WRITE link share shows add-task', async ({page}) => { + await UserFactory.create(1) + const projects = await createProjects() + await TaskFactory.create(3, { + project_id: projects[0].id, + }) + const [share] = await LinkShareFactory.create(1, { + project_id: projects[0].id, + permission: 1, + }) + + await page.goto(`/share/${share.hash}/auth`) + + await expect(page.locator('h1.title')).toContainText(projects[0].title) + await expect(page).toHaveURL(`/projects/${projects[0].id}/1#share-auth-token=${share.hash}`) + + await expect(page.locator('.input[placeholder="Add a task…"]')).toBeVisible() + }) +}) From c0101afb5913d1dde81b0a887d2cb20988a547b5 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 12:18:08 +0200 Subject: [PATCH 451/501] test(e2e): widen recurrence due-date tolerance to 5s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI shard 4 hit a ~996ms skew between the JS-constructed originalDue and the backend's advanced due date, enough to bust the <500ms precision bound. Bump precision to -4 (<5s) — still tight enough to confirm the regeneration advanced by ~1 day, loose enough to absorb sub-second round-tripping through Date → ISO → Go time.Time → JSON. --- frontend/tests/e2e/task/recurrence.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/tests/e2e/task/recurrence.spec.ts b/frontend/tests/e2e/task/recurrence.spec.ts index 76101c250..e748869f8 100644 --- a/frontend/tests/e2e/task/recurrence.spec.ts +++ b/frontend/tests/e2e/task/recurrence.spec.ts @@ -56,7 +56,9 @@ test.describe('Task recurrence', () => { const newDue = new Date(refreshed.due_date).getTime() // addRepeatIntervalToTime: when the original due date is still in the // future, the backend advances it by exactly one interval (86400s here). - expect(newDue - originalDue.getTime()).toBeCloseTo(86_400_000, -3) + // Tolerance of <5s absorbs sub-second timestamp round-tripping between + // the JS Date → ISO string → backend time.Time → JSON response path. + expect(newDue - originalDue.getTime()).toBeCloseTo(86_400_000, -4) }) test('monthly repeat mode hides the amount field', async ({authenticatedPage: page}) => { From be28ec70d82dd0459e46e289abef32ab1efef130 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 12:26:39 +0200 Subject: [PATCH 452/501] test(e2e): await DELETE in session revoke test to avoid race --- frontend/tests/e2e/user/settings/sessions.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/tests/e2e/user/settings/sessions.spec.ts b/frontend/tests/e2e/user/settings/sessions.spec.ts index 5b4305a9b..c5161a653 100644 --- a/frontend/tests/e2e/user/settings/sessions.spec.ts +++ b/frontend/tests/e2e/user/settings/sessions.spec.ts @@ -35,7 +35,11 @@ test.describe('Sessions', () => { await gotoUserSettings(page, 'sessions') await page.locator('tr', {hasText: /192\.0\.2\.5/}) .getByRole('button', {name: 'Delete'}).click() + const deleted = page.waitForResponse(r => + /\/user\/sessions\/[^/]+/.test(r.url()) && r.request().method() === 'DELETE', + ) await page.locator('dialog[open] .modal-content .actions .button').filter({hasText: 'Do it!'}).click() + await deleted await expect(page.locator('table.table tbody tr')).toHaveCount(1) // After revoke, the refresh request must fail. Refresh tokens live in the From b90e67d7ca009e5a893271fdd33f346b2b2b193b Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 12:26:40 +0200 Subject: [PATCH 453/501] test(e2e): await DELETE in caldav token revoke test to avoid race --- frontend/tests/e2e/user/settings/caldav.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/tests/e2e/user/settings/caldav.spec.ts b/frontend/tests/e2e/user/settings/caldav.spec.ts index b4efa99a8..dfc839807 100644 --- a/frontend/tests/e2e/user/settings/caldav.spec.ts +++ b/frontend/tests/e2e/user/settings/caldav.spec.ts @@ -43,7 +43,11 @@ test.describe('CalDAV', () => { const dataRows = page.locator('table.table tr').filter({has: page.locator('td')}) await expect(dataRows).toHaveCount(1) + const deleted = page.waitForResponse(r => + /\/user\/settings\/token\/caldav\/\d+/.test(r.url()) && r.request().method() === 'DELETE', + ) await dataRows.getByRole('button', {name: 'Delete'}).click() + await deleted await expect(dataRows).toHaveCount(0) // NOTE: the factory seeds the plaintext token as-is, but caldav tokens are From 9d25864b2526c732849d0b0acae2d458034ca789 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:00:08 +0000 Subject: [PATCH 454/501] chore(deps): pin dependency otplib to 12.0.1 --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 43426558c..9f42f6898 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -133,7 +133,7 @@ "eslint-plugin-vue": "10.8.0", "happy-dom": "20.9.0", "histoire": "1.0.0-beta.1", - "otplib": "^12.0.1", + "otplib": "12.0.1", "postcss": "8.5.10", "postcss-easing-gradients": "3.0.1", "postcss-preset-env": "11.2.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 71c9bc264..427fe60cd 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -257,7 +257,7 @@ importers: specifier: 1.0.0-beta.1 version: 1.0.0-beta.1(@types/node@24.12.2)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) otplib: - specifier: ^12.0.1 + specifier: 12.0.1 version: 12.0.1 postcss: specifier: 8.5.10 From eb441f8b0cca9163e74f04b01d3888511b7a0943 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 12 Apr 2026 14:11:17 +0200 Subject: [PATCH 455/501] feat(a11y): add i18n keys for accessibility labels --- frontend/src/i18n/lang/en.json | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 7b57754c5..df9de05cf 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -500,7 +500,8 @@ "bucketTitleSavedSuccess": "The bucket title has been saved successfully.", "bucketLimitSavedSuccess": "The bucket limit been saved successfully.", "collapse": "Collapse this bucket", - "bucketLimitReached": "You have reached the bucket limit. Remove tasks or increase the limit to add new tasks." + "bucketLimitReached": "You have reached the bucket limit. Remove tasks or increase the limit to add new tasks.", + "bucketOptions": "Bucket options" }, "pseudo": { "favorites": { @@ -740,7 +741,9 @@ "upcoming": "Upcoming", "settings": "Settings", "imprint": "Imprint", - "privacy": "Privacy Policy" + "privacy": "Privacy Policy", + "closeSidebar": "Close sidebar", + "home": "Vikunja home" }, "misc": { "loading": "Loading…", @@ -775,9 +778,15 @@ "createdBy": "Created by {0}", "actions": "Actions", "cannotBeUndone": "This cannot be undone!", - "avatarOfUser": "{user}'s profile image" + "avatarOfUser": "{user}'s profile image", + "closeBanner": "Close banner", + "closeDialog": "Close dialog", + "closeQuickActions": "Close quick actions", + "skipToContent": "Skip to main content", + "sortBy": "Sort by" }, "input": { + "projectColor": "Project color", "resetColor": "Reset Color", "datepicker": { "today": "Today", @@ -972,6 +981,8 @@ "back": "Back to project", "due": "Due {at}", "closePopup": "Close popup", + "closeTaskDetail": "Close task detail", + "markAsDone": "Mark '{task}' as done", "scrollToBottom": "Scroll to bottom", "organization": "Organization", "management": "Management", From 732b65ba7c324a67fae85ba35d70b72f35d869f4 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 12 Apr 2026 14:12:32 +0200 Subject: [PATCH 456/501] feat(a11y): add skip navigation link and main landmark on auth pages Adds a visually-hidden skip-to-content link as the first focusable element. Adds id='main-content' to the
element. Changes
to
on auth pages for proper landmark navigation. Fixes WCAG 2.4.1 (Bypass Blocks). --- frontend/src/App.vue | 6 ++++++ frontend/src/components/home/ContentAuth.vue | 1 + frontend/src/components/misc/NoAuthWrapper.vue | 7 +++++-- frontend/src/styles/global.scss | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 47ad77d43..08688b1bb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -9,6 +9,12 @@