From 8052fda8400d9c4af7a0335fae68e2736592a57c Mon Sep 17 00:00:00 2001 From: Prospector Date: Thu, 24 Jul 2025 10:37:01 -0700 Subject: [PATCH 01/59] Bump report limit to 1500 --- .../src/components/ui/report/ReportsList.vue | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/frontend/src/components/ui/report/ReportsList.vue b/apps/frontend/src/components/ui/report/ReportsList.vue index 09e1f4f8..557a75f2 100644 --- a/apps/frontend/src/components/ui/report/ReportsList.vue +++ b/apps/frontend/src/components/ui/report/ReportsList.vue @@ -1,13 +1,21 @@ From 6db1d66591da292bdde0273544fbe971a1e7a37a Mon Sep 17 00:00:00 2001 From: Prospector Date: Thu, 24 Jul 2025 10:38:23 -0700 Subject: [PATCH 02/59] else if --- apps/frontend/src/components/ui/report/ReportsList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/ui/report/ReportsList.vue b/apps/frontend/src/components/ui/report/ReportsList.vue index 557a75f2..3c77adda 100644 --- a/apps/frontend/src/components/ui/report/ReportsList.vue +++ b/apps/frontend/src/components/ui/report/ReportsList.vue @@ -5,7 +5,7 @@ There are at least {{ MAX_REPORTS }} open reports. This page is at its max reports and will not show any more recent ones.

-

There are {{ filteredReports.length }} open reports.

+

There are {{ filteredReports.length }} open reports.

There are {{ filteredReports.length }}/{{ reports.length }} open '{{ reasonFilter }}' reports.

From 358cf31c873644ae3b356b7e37e1e6b780f895d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= <7822554+AlexTMjugador@users.noreply.github.com> Date: Sat, 26 Jul 2025 14:32:35 +0200 Subject: [PATCH 03/59] feat(labrinth): basic offset pagination for moderation reports and projects (#4063) --- ...0598aa812c8ef0f5895fa740859321092a1c.json} | 5 +++-- ...4da31afd91d4b480527e2dd8402aef36f12c.json} | 5 +++-- ...288fc9f8e77ffd7bce665441ff682384cbf9.json} | 5 +++-- ...0725230041_reports-closed-status-index.sql | 1 + .../src/routes/internal/moderation.rs | 16 +++++++++----- apps/labrinth/src/routes/v2/moderation.rs | 9 +++++--- apps/labrinth/src/routes/v2/reports.rs | 11 +++++----- apps/labrinth/src/routes/v3/reports.rs | 22 ++++++++++++------- 8 files changed, 46 insertions(+), 28 deletions(-) rename apps/labrinth/.sqlx/{query-29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217.json => query-1aea0d5e6936b043cb7727b779d60598aa812c8ef0f5895fa740859321092a1c.json} (73%) rename apps/labrinth/.sqlx/{query-f17a109913015a7a5ab847bb2e73794d6261a08d450de24b450222755e520881.json => query-be8a5dd2b71fdc279a6fa68fe5384da31afd91d4b480527e2dd8402aef36f12c.json} (71%) rename apps/labrinth/.sqlx/{query-3baabc9f08401801fa290866888c540746fc50c1d79911f08f3322b605ce5c30.json => query-ccb0315ff52ea4402f53508334a7288fc9f8e77ffd7bce665441ff682384cbf9.json} (66%) create mode 100644 apps/labrinth/migrations/20250725230041_reports-closed-status-index.sql diff --git a/apps/labrinth/.sqlx/query-29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217.json b/apps/labrinth/.sqlx/query-1aea0d5e6936b043cb7727b779d60598aa812c8ef0f5895fa740859321092a1c.json similarity index 73% rename from apps/labrinth/.sqlx/query-29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217.json rename to apps/labrinth/.sqlx/query-1aea0d5e6936b043cb7727b779d60598aa812c8ef0f5895fa740859321092a1c.json index 75f3f2d9..f15925ae 100644 --- a/apps/labrinth/.sqlx/query-29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217.json +++ b/apps/labrinth/.sqlx/query-1aea0d5e6936b043cb7727b779d60598aa812c8ef0f5895fa740859321092a1c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id FROM reports\n WHERE closed = FALSE\n ORDER BY created ASC\n LIMIT $1;\n ", + "query": "\n SELECT id FROM reports\n WHERE closed = FALSE\n ORDER BY created ASC\n OFFSET $2\n LIMIT $1\n ", "describe": { "columns": [ { @@ -11,6 +11,7 @@ ], "parameters": { "Left": [ + "Int8", "Int8" ] }, @@ -18,5 +19,5 @@ false ] }, - "hash": "29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217" + "hash": "1aea0d5e6936b043cb7727b779d60598aa812c8ef0f5895fa740859321092a1c" } diff --git a/apps/labrinth/.sqlx/query-f17a109913015a7a5ab847bb2e73794d6261a08d450de24b450222755e520881.json b/apps/labrinth/.sqlx/query-be8a5dd2b71fdc279a6fa68fe5384da31afd91d4b480527e2dd8402aef36f12c.json similarity index 71% rename from apps/labrinth/.sqlx/query-f17a109913015a7a5ab847bb2e73794d6261a08d450de24b450222755e520881.json rename to apps/labrinth/.sqlx/query-be8a5dd2b71fdc279a6fa68fe5384da31afd91d4b480527e2dd8402aef36f12c.json index 40600b2f..c5157ca7 100644 --- a/apps/labrinth/.sqlx/query-f17a109913015a7a5ab847bb2e73794d6261a08d450de24b450222755e520881.json +++ b/apps/labrinth/.sqlx/query-be8a5dd2b71fdc279a6fa68fe5384da31afd91d4b480527e2dd8402aef36f12c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id FROM reports\n WHERE closed = FALSE AND reporter = $1\n ORDER BY created ASC\n LIMIT $2;\n ", + "query": "\n SELECT id FROM reports\n WHERE closed = FALSE AND reporter = $1\n ORDER BY created ASC\n OFFSET $3\n LIMIT $2\n ", "describe": { "columns": [ { @@ -11,6 +11,7 @@ ], "parameters": { "Left": [ + "Int8", "Int8", "Int8" ] @@ -19,5 +20,5 @@ false ] }, - "hash": "f17a109913015a7a5ab847bb2e73794d6261a08d450de24b450222755e520881" + "hash": "be8a5dd2b71fdc279a6fa68fe5384da31afd91d4b480527e2dd8402aef36f12c" } diff --git a/apps/labrinth/.sqlx/query-3baabc9f08401801fa290866888c540746fc50c1d79911f08f3322b605ce5c30.json b/apps/labrinth/.sqlx/query-ccb0315ff52ea4402f53508334a7288fc9f8e77ffd7bce665441ff682384cbf9.json similarity index 66% rename from apps/labrinth/.sqlx/query-3baabc9f08401801fa290866888c540746fc50c1d79911f08f3322b605ce5c30.json rename to apps/labrinth/.sqlx/query-ccb0315ff52ea4402f53508334a7288fc9f8e77ffd7bce665441ff682384cbf9.json index 5aef0080..81d4a51d 100644 --- a/apps/labrinth/.sqlx/query-3baabc9f08401801fa290866888c540746fc50c1d79911f08f3322b605ce5c30.json +++ b/apps/labrinth/.sqlx/query-ccb0315ff52ea4402f53508334a7288fc9f8e77ffd7bce665441ff682384cbf9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY queued ASC\n LIMIT $2;\n ", + "query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY queued ASC\n OFFSET $3\n LIMIT $2\n ", "describe": { "columns": [ { @@ -12,6 +12,7 @@ "parameters": { "Left": [ "Text", + "Int8", "Int8" ] }, @@ -19,5 +20,5 @@ false ] }, - "hash": "3baabc9f08401801fa290866888c540746fc50c1d79911f08f3322b605ce5c30" + "hash": "ccb0315ff52ea4402f53508334a7288fc9f8e77ffd7bce665441ff682384cbf9" } diff --git a/apps/labrinth/migrations/20250725230041_reports-closed-status-index.sql b/apps/labrinth/migrations/20250725230041_reports-closed-status-index.sql new file mode 100644 index 00000000..5a3b5846 --- /dev/null +++ b/apps/labrinth/migrations/20250725230041_reports-closed-status-index.sql @@ -0,0 +1 @@ +CREATE INDEX reports_closed ON reports (closed); diff --git a/apps/labrinth/src/routes/internal/moderation.rs b/apps/labrinth/src/routes/internal/moderation.rs index 797d1133..ff28901f 100644 --- a/apps/labrinth/src/routes/internal/moderation.rs +++ b/apps/labrinth/src/routes/internal/moderation.rs @@ -18,12 +18,14 @@ pub fn config(cfg: &mut web::ServiceConfig) { } #[derive(Deserialize)] -pub struct ResultCount { +pub struct ProjectsRequestOptions { #[serde(default = "default_count")] - pub count: i16, + pub count: u16, + #[serde(default)] + pub offset: u32, } -fn default_count() -> i16 { +fn default_count() -> u16 { 100 } @@ -31,7 +33,7 @@ pub async fn get_projects( req: HttpRequest, pool: web::Data, redis: web::Data, - count: web::Query, + request_opts: web::Query, session_queue: web::Data, ) -> Result { check_is_moderator_from_headers( @@ -50,10 +52,12 @@ pub async fn get_projects( SELECT id FROM mods WHERE status = $1 ORDER BY queued ASC - LIMIT $2; + OFFSET $3 + LIMIT $2 ", ProjectStatus::Processing.as_str(), - count.count as i64 + request_opts.count as i64, + request_opts.offset as i64 ) .fetch(&**pool) .map_ok(|m| database::models::DBProjectId(m.id)) diff --git a/apps/labrinth/src/routes/v2/moderation.rs b/apps/labrinth/src/routes/v2/moderation.rs index 7b5240c1..ff721e6c 100644 --- a/apps/labrinth/src/routes/v2/moderation.rs +++ b/apps/labrinth/src/routes/v2/moderation.rs @@ -15,10 +15,10 @@ pub fn config(cfg: &mut web::ServiceConfig) { #[derive(Deserialize)] pub struct ResultCount { #[serde(default = "default_count")] - pub count: i16, + pub count: u16, } -fn default_count() -> i16 { +fn default_count() -> u16 { 100 } @@ -34,7 +34,10 @@ pub async fn get_projects( req, pool.clone(), redis.clone(), - web::Query(internal::moderation::ResultCount { count: count.count }), + web::Query(internal::moderation::ProjectsRequestOptions { + count: count.count, + offset: 0, + }), session_queue, ) .await diff --git a/apps/labrinth/src/routes/v2/reports.rs b/apps/labrinth/src/routes/v2/reports.rs index 2f09ed3c..8804c47a 100644 --- a/apps/labrinth/src/routes/v2/reports.rs +++ b/apps/labrinth/src/routes/v2/reports.rs @@ -43,12 +43,12 @@ pub async fn report_create( #[derive(Deserialize)] pub struct ReportsRequestOptions { #[serde(default = "default_count")] - count: i16, + count: u16, #[serde(default = "default_all")] all: bool, } -fn default_count() -> i16 { +fn default_count() -> u16 { 100 } fn default_all() -> bool { @@ -60,7 +60,7 @@ pub async fn reports( req: HttpRequest, pool: web::Data, redis: web::Data, - count: web::Query, + request_opts: web::Query, session_queue: web::Data, ) -> Result { let response = v3::reports::reports( @@ -68,8 +68,9 @@ pub async fn reports( pool, redis, web::Query(v3::reports::ReportsRequestOptions { - count: count.count, - all: count.all, + count: request_opts.count, + offset: 0, + all: request_opts.all, }), session_queue, ) diff --git a/apps/labrinth/src/routes/v3/reports.rs b/apps/labrinth/src/routes/v3/reports.rs index 8708054a..ee009444 100644 --- a/apps/labrinth/src/routes/v3/reports.rs +++ b/apps/labrinth/src/routes/v3/reports.rs @@ -222,12 +222,14 @@ pub async fn report_create( #[derive(Deserialize)] pub struct ReportsRequestOptions { #[serde(default = "default_count")] - pub count: i16, + pub count: u16, + #[serde(default)] + pub offset: u32, #[serde(default = "default_all")] pub all: bool, } -fn default_count() -> i16 { +fn default_count() -> u16 { 100 } fn default_all() -> bool { @@ -238,7 +240,7 @@ pub async fn reports( req: HttpRequest, pool: web::Data, redis: web::Data, - count: web::Query, + request_opts: web::Query, session_queue: web::Data, ) -> Result { let user = get_user_from_headers( @@ -253,15 +255,17 @@ pub async fn reports( use futures::stream::TryStreamExt; - let report_ids = if user.role.is_mod() && count.all { + let report_ids = if user.role.is_mod() && request_opts.all { sqlx::query!( " SELECT id FROM reports WHERE closed = FALSE ORDER BY created ASC - LIMIT $1; + OFFSET $2 + LIMIT $1 ", - count.count as i64 + request_opts.count as i64, + request_opts.offset as i64 ) .fetch(&**pool) .map_ok(|m| crate::database::models::ids::DBReportId(m.id)) @@ -273,10 +277,12 @@ pub async fn reports( SELECT id FROM reports WHERE closed = FALSE AND reporter = $1 ORDER BY created ASC - LIMIT $2; + OFFSET $3 + LIMIT $2 ", user.id.0 as i64, - count.count as i64 + request_opts.count as i64, + request_opts.offset as i64 ) .fetch(&**pool) .map_ok(|m| crate::database::models::ids::DBReportId(m.id)) From 5deb4179ad8a08d11cc840843fa9e6abb3f71ebe Mon Sep 17 00:00:00 2001 From: Emma Alexia Date: Sun, 27 Jul 2025 13:07:39 -0400 Subject: [PATCH 04/59] Re-enable the Moderation tab for projects that are approved (#4067) By request of the moderation team. This would allow easier access if, e.g., the moderators tell the author of a metadata problem they need to correct. --- .../ui/thread/ConversationThread.vue | 53 ++++++++++++++++++- apps/frontend/src/pages/[type]/[id].vue | 13 +---- .../src/pages/[type]/[id]/moderation.vue | 11 +++- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/apps/frontend/src/components/ui/thread/ConversationThread.vue b/apps/frontend/src/components/ui/thread/ConversationThread.vue index 9109822e..13be399c 100644 --- a/apps/frontend/src/components/ui/thread/ConversationThread.vue +++ b/apps/frontend/src/components/ui/thread/ConversationThread.vue @@ -34,6 +34,38 @@ + + +
Thread ID: @@ -71,12 +103,17 @@ v-if="sortedMessages.length > 0" class="btn btn-primary" :disabled="!replyBody" - @click="sendReply()" + @click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()" >
+
+
+ Your server has been cancelled. Please + update your billing information or contact Modrinth Support for more information. +
+ +
+
+
+ Your server has been suspended: + {{ suspension_reason }}. Please update your billing information or contact Modrinth Support + for more information. +
+ +
>(); -if (props.server_id) { +if (props.server_id && props.status === "available") { + // Necessary only to get server icon await useModrinthServers(props.server_id, ["general"]); } @@ -109,11 +131,6 @@ if (props.upstream) { } const image = useState(`server-icon-${props.server_id}`, () => undefined); - -if (import.meta.server && projectData.value?.icon_url) { - await useModrinthServers(props.server_id!, ["general"]); -} - const iconUrl = computed(() => projectData.value?.icon_url || undefined); const isConfiguring = computed(() => props.flows?.intro); diff --git a/apps/frontend/src/pages/servers/manage/index.vue b/apps/frontend/src/pages/servers/manage/index.vue index f4d512fc..dd5d3db6 100644 --- a/apps/frontend/src/pages/servers/manage/index.vue +++ b/apps/frontend/src/pages/servers/manage/index.vue @@ -96,16 +96,7 @@ diff --git a/apps/frontend/src/pages/settings/billing/index.vue b/apps/frontend/src/pages/settings/billing/index.vue index 96ace83c..09ccbaf3 100644 --- a/apps/frontend/src/pages/settings/billing/index.vue +++ b/apps/frontend/src/pages/settings/billing/index.vue @@ -208,15 +208,7 @@

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa0901fe..a37c9043 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -581,7 +581,7 @@ importers: version: 7.3.1 '@vintl/unplugin': specifier: ^1.5.1 - version: 1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) + version: 1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) '@vintl/vintl': specifier: ^4.4.1 version: 4.4.1(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)) @@ -1844,6 +1844,9 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1856,15 +1859,24 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.10': + resolution: {integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==} + '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -3500,8 +3512,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.25.0: - resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3534,8 +3546,8 @@ packages: magicast: optional: true - c12@3.0.4: - resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} peerDependencies: magicast: ^0.3.5 peerDependenciesMeta: @@ -3587,8 +3599,8 @@ packages: caniuse-lite@1.0.30001687: resolution: {integrity: sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==} - caniuse-lite@1.0.30001723: - resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4151,8 +4163,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.167: - resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==} + electron-to-chromium@1.5.191: + resolution: {integrity: sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==} electron-to-chromium@1.5.71: resolution: {integrity: sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==} @@ -4187,8 +4199,8 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + enhanced-resolve@5.18.2: + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} engines: {node: '>=10.13.0'} entities@2.2.0: @@ -5402,6 +5414,10 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -6354,6 +6370,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -6388,8 +6408,8 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.1.1: - resolution: {integrity: sha512-eY0QFb6eSwc9+0d/5D2lFFUq+A3n3QNGSy/X2Nvp+6MfzGw2u6EbA7S80actgjY1lkvvI0pqB+a4hioMh443Ew==} + pkg-types@2.2.0: + resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} @@ -6606,8 +6626,8 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.5: - resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} posthog-js@1.158.2: @@ -7491,6 +7511,11 @@ packages: engines: {node: '>=10'} hasBin: true + terser@5.43.1: + resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + engines: {node: '>=10'} + hasBin: true + text-decoder@1.1.0: resolution: {integrity: sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==} @@ -7738,8 +7763,8 @@ packages: unimport@3.14.4: resolution: {integrity: sha512-90jQsiS2D0vIrWg4U58do7B5Hr4q0qt9o/rS0TrDMzrvNuAQ7XF1sQ47Pe2zjVlvFWNkoPBb/2l2GJFy5XjqDg==} - unimport@5.1.0: - resolution: {integrity: sha512-wMmuG+wkzeHh2KCE6yiDlHmKelN8iE/maxkUYMbmrS6iV8+n6eP1TH3yKKlepuF4hrkepinEGmBXdfo9XZUvAw==} + unimport@5.2.0: + resolution: {integrity: sha512-bTuAMMOOqIAyjV4i4UH7P07pO+EsVxmhOzQ2YJ290J6mkLUdozNhb5I/YoOEheeNADC03ent3Qj07X0fWfUpmw==} engines: {node: '>=18.12.0'} unist-util-find-after@5.0.0: @@ -8353,8 +8378,8 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webpack-sources@3.3.2: - resolution: {integrity: sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==} + webpack-sources@3.3.3: + resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} webpack-virtual-modules@0.6.2: @@ -9339,7 +9364,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@9.4.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -9515,7 +9540,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@9.4.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9616,6 +9641,12 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 + optional: true + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -9626,6 +9657,12 @@ snapshots: '@jridgewell/set-array@1.2.1': {} + '@jridgewell/source-map@0.3.10': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + optional: true + '@jridgewell/source-map@0.3.6': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -9633,11 +9670,20 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.4': + optional: true + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + optional: true + '@jsdevtools/ono@7.1.3': {} '@kwsites/file-exists@1.1.1': @@ -9885,27 +9931,27 @@ snapshots: '@nuxt/kit@3.17.5(magicast@0.3.5)': dependencies: - c12: 3.0.4(magicast@0.3.5) + c12: 3.1.0(magicast@0.3.5) consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 errx: 0.1.0 exsolve: 1.0.7 ignore: 7.0.5 - jiti: 2.4.2 + jiti: 2.5.1 klona: 2.0.6 knitwork: 1.2.0 mlly: 1.7.4 ohash: 2.0.11 pathe: 2.0.3 - pkg-types: 2.1.1 + pkg-types: 2.2.0 scule: 1.3.0 semver: 7.7.2 std-env: 3.9.0 tinyglobby: 0.2.14 ufo: 1.6.1 unctx: 2.4.1 - unimport: 5.1.0 + unimport: 5.2.0 untyped: 2.0.0 transitivePeerDependencies: - magicast @@ -11100,7 +11146,7 @@ snapshots: - vue - webpack - '@vintl/unplugin@1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': + '@vintl/unplugin@1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': dependencies: '@formatjs/cli-lib': 6.4.2(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4)) '@formatjs/icu-messageformat-parser': 2.7.8 @@ -11111,7 +11157,7 @@ snapshots: unplugin: 1.16.0 optionalDependencies: rollup: 3.29.4 - vite: 4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0) + vite: 4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1) webpack: 5.92.1 transitivePeerDependencies: - '@glimmer/env' @@ -11961,12 +12007,12 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) - browserslist@4.25.0: + browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001723 - electron-to-chromium: 1.5.167 + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.191 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.0) + update-browserslist-db: 1.1.3(browserslist@4.25.1) optional: true buffer-crc32@1.0.0: {} @@ -12005,7 +12051,7 @@ snapshots: optionalDependencies: magicast: 0.3.5 - c12@3.0.4(magicast@0.3.5): + c12@3.1.0(magicast@0.3.5): dependencies: chokidar: 4.0.3 confbox: 0.2.2 @@ -12013,11 +12059,11 @@ snapshots: dotenv: 16.6.1 exsolve: 1.0.7 giget: 2.0.0 - jiti: 2.4.2 + jiti: 2.5.1 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 2.1.1 + pkg-types: 2.2.0 rc9: 2.1.2 optionalDependencies: magicast: 0.3.5 @@ -12069,7 +12115,7 @@ snapshots: caniuse-lite@1.0.30001687: {} - caniuse-lite@1.0.30001723: + caniuse-lite@1.0.30001727: optional: true ccount@2.0.1: {} @@ -12399,10 +12445,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: - dependencies: - ms: 2.1.3 - debug@4.4.0(supports-color@9.4.0): dependencies: ms: 2.1.3 @@ -12545,7 +12587,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.167: + electron-to-chromium@1.5.191: optional: true electron-to-chromium@1.5.71: {} @@ -12576,7 +12618,7 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 - enhanced-resolve@5.18.1: + enhanced-resolve@5.18.2: dependencies: graceful-fs: 4.2.11 tapable: 2.2.2 @@ -13165,7 +13207,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -13400,9 +13442,9 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - fdir@6.4.6(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 optional: true fflate@0.4.8: {} @@ -14275,7 +14317,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.11 + '@types/node': 22.4.1 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true @@ -14284,6 +14326,9 @@ snapshots: jiti@2.4.2: {} + jiti@2.5.1: + optional: true + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -14448,7 +14493,7 @@ snapshots: local-pkg@1.1.1: dependencies: mlly: 1.7.4 - pkg-types: 2.1.1 + pkg-types: 2.2.0 quansync: 0.2.10 optional: true @@ -15445,7 +15490,7 @@ snapshots: citty: 0.1.6 consola: 3.4.2 pathe: 2.0.3 - pkg-types: 2.1.1 + pkg-types: 2.2.0 tinyexec: 0.3.2 optional: true @@ -15702,6 +15747,9 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: + optional: true + pify@2.3.0: {} pify@4.0.1: {} @@ -15733,7 +15781,7 @@ snapshots: pathe: 2.0.3 optional: true - pkg-types@2.1.1: + pkg-types@2.2.0: dependencies: confbox: 0.2.2 exsolve: 1.0.7 @@ -15936,7 +15984,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.5: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -16991,11 +17039,11 @@ snapshots: terser-webpack-plugin@5.3.14(webpack@5.92.1): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - terser: 5.42.0 + terser: 5.43.1 webpack: 5.92.1 optional: true @@ -17006,6 +17054,14 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + terser@5.43.1: + dependencies: + '@jridgewell/source-map': 0.3.10 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + optional: true + text-decoder@1.1.0: dependencies: b4a: 1.6.6 @@ -17063,8 +17119,8 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 optional: true to-regex-range@5.0.1: @@ -17271,7 +17327,7 @@ snapshots: transitivePeerDependencies: - rollup - unimport@5.1.0: + unimport@5.2.0: dependencies: acorn: 8.15.0 escape-string-regexp: 5.0.0 @@ -17280,8 +17336,8 @@ snapshots: magic-string: 0.30.17 mlly: 1.7.4 pathe: 2.0.3 - picomatch: 4.0.2 - pkg-types: 2.1.1 + picomatch: 4.0.3 + pkg-types: 2.2.0 scule: 1.3.0 strip-literal: 3.0.0 tinyglobby: 0.2.14 @@ -17340,7 +17396,7 @@ snapshots: unplugin-utils@0.2.4: dependencies: pathe: 2.0.3 - picomatch: 4.0.2 + picomatch: 4.0.3 optional: true unplugin-vue-router@0.10.9(rollup@4.28.1)(vue-router@4.5.0(vue@3.5.13(typescript@5.5.4)))(vue@3.5.13(typescript@5.5.4)): @@ -17378,7 +17434,7 @@ snapshots: unplugin@2.3.5: dependencies: acorn: 8.15.0 - picomatch: 4.0.2 + picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 optional: true @@ -17432,7 +17488,7 @@ snapshots: dependencies: citty: 0.1.6 defu: 6.1.4 - jiti: 2.4.2 + jiti: 2.5.1 knitwork: 1.2.0 scule: 1.3.0 optional: true @@ -17452,9 +17508,9 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.1.3(browserslist@4.25.0): + update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: - browserslist: 4.25.0 + browserslist: 4.25.1 escalade: 3.2.0 picocolors: 1.1.1 optional: true @@ -17576,16 +17632,16 @@ snapshots: svgo: 3.3.2 vue: 3.5.13(typescript@5.5.4) - vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0): + vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1): dependencies: esbuild: 0.18.20 - postcss: 8.5.5 + postcss: 8.5.6 rollup: 3.29.4 optionalDependencies: '@types/node': 22.4.1 fsevents: 2.3.3 sass: 1.77.6 - terser: 5.42.0 + terser: 5.43.1 optional: true vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0): @@ -17861,7 +17917,7 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-sources@3.3.2: + webpack-sources@3.3.3: optional: true webpack-virtual-modules@0.6.2: {} @@ -17875,9 +17931,9 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) - browserslist: 4.25.0 + browserslist: 4.25.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.1 + enhanced-resolve: 5.18.2 es-module-lexer: 1.7.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -17891,7 +17947,7 @@ snapshots: tapable: 2.2.2 terser-webpack-plugin: 5.3.14(webpack@5.92.1) watchpack: 2.4.4 - webpack-sources: 3.3.2 + webpack-sources: 3.3.3 transitivePeerDependencies: - '@swc/core' - esbuild From 7dffb352d54e591c6425b2e22f349cc030e38729 Mon Sep 17 00:00:00 2001 From: Emma Alexia Date: Sun, 27 Jul 2025 13:27:02 -0400 Subject: [PATCH 06/59] Fix duplicate "Upload icon Select file" on collections (#4069) * Fix duplicate "Upload icon Select file" on collections ![lol](https://i.imgur.com/NKfvfQD.png) * fix lint --- apps/frontend/src/locales/en-US/index.json | 3 --- apps/frontend/src/pages/collection/[id].vue | 5 ----- 2 files changed, 8 deletions(-) diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index e6a708f7..38e72448 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -182,9 +182,6 @@ "collection.button.unfollow-project": { "message": "Unfollow project" }, - "collection.button.upload-icon": { - "message": "Upload icon" - }, "collection.delete-modal.description": { "message": "This will remove this collection forever. This action cannot be undone." }, diff --git a/apps/frontend/src/pages/collection/[id].vue b/apps/frontend/src/pages/collection/[id].vue index 6f522ee3..a738d43e 100644 --- a/apps/frontend/src/pages/collection/[id].vue +++ b/apps/frontend/src/pages/collection/[id].vue @@ -40,7 +40,6 @@ @change="showPreviewImage" >

+ + +
+
+ + +
+
+ + + + + + +
+
+
+ + +
@@ -234,7 +275,6 @@ import { products } from "~/generated/state.json"; import ModrinthServersIcon from "~/components/ui/servers/ModrinthServersIcon.vue"; const route = useRoute(); -const data = useNuxtApp(); const vintl = useVIntl(); const { formatMessage } = vintl; @@ -304,6 +344,10 @@ const refundTypes = ref(["full", "partial", "none"]); const refundAmount = ref(0); const unprovision = ref(true); +const modifying = ref(false); +const modifyModal = ref(); +const cancel = ref(false); + function showRefundModal(charge) { selectedCharge.value = charge; refundType.value = "full"; @@ -312,6 +356,12 @@ function showRefundModal(charge) { refundModal.value.show(); } +function showModifyModal(charge) { + selectedCharge.value = charge; + cancel.value = false; + modifyModal.value.show(); +} + async function refundCharge() { refunding.value = true; try { @@ -327,8 +377,7 @@ async function refundCharge() { await refreshCharges(); refundModal.value.hide(); } catch (err) { - data.$notify({ - group: "main", + addNotification({ title: "Error refunding", text: err.data?.description ?? err, type: "error", @@ -337,6 +386,32 @@ async function refundCharge() { refunding.value = false; } +async function modifyCharge() { + modifying.value = true; + try { + await useBaseFetch(`billing/subscription/${selectedCharge.value.id}`, { + method: "PATCH", + body: JSON.stringify({ + cancelled: cancel.value, + }), + internal: true, + }); + addNotification({ + title: "Resubscription request submitted", + text: "If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.", + type: "success", + }); + await refreshCharges(); + } catch (err) { + addNotification({ + title: "Error reattempting charge", + text: err.data?.description ?? err, + type: "error", + }); + } + modifying.value = false; +} + const chargeStatuses = { open: { color: "bg-blue", diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index a5fcbc17..49e50eac 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -276,7 +276,11 @@ pub async fn refund_charge( subscription_interval: charge.subscription_interval, payment_platform: charge.payment_platform, payment_platform_id: id, - parent_charge_id: Some(charge.id), + parent_charge_id: if refund_amount != 0 { + Some(charge.id) + } else { + None + }, net, } .upsert(&mut transaction) From b8982a6d17eb1408b51eeb19e9216120f04908ec Mon Sep 17 00:00:00 2001 From: Emma Alexia Date: Sun, 27 Jul 2025 14:23:49 -0400 Subject: [PATCH 08/59] Hopefully fix collection visibility once and for all (#4070) * Hopefully fix collection visibility once and for all Follow up to #3408 and #3864 * Use same unlisted approach for collections as is used for projects --- apps/labrinth/src/auth/checks.rs | 15 ++++++++-- apps/labrinth/src/models/v3/collections.rs | 7 ++++- apps/labrinth/src/routes/v3/collections.rs | 5 ++-- apps/labrinth/src/routes/v3/users.rs | 34 +++++++++------------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/apps/labrinth/src/auth/checks.rs b/apps/labrinth/src/auth/checks.rs index 7cb64438..662a8b09 100644 --- a/apps/labrinth/src/auth/checks.rs +++ b/apps/labrinth/src/auth/checks.rs @@ -315,9 +315,13 @@ pub async fn filter_enlisted_version_ids( pub async fn is_visible_collection( collection_data: &DBCollection, user_option: &Option, + hide_unlisted: bool, ) -> Result { - let mut authorized = !collection_data.status.is_hidden() - && !collection_data.projects.is_empty(); + let mut authorized = (if hide_unlisted { + collection_data.status.is_searchable() + } else { + !collection_data.status.is_hidden() + }) && !collection_data.projects.is_empty(); if let Some(user) = &user_option { if !authorized && (user.role.is_mod() || user.id == collection_data.user_id.into()) @@ -331,12 +335,17 @@ pub async fn is_visible_collection( pub async fn filter_visible_collections( collections: Vec, user_option: &Option, + hide_unlisted: bool, ) -> Result, ApiError> { let mut return_collections = Vec::new(); let mut check_collections = Vec::new(); for collection in collections { - if (!collection.status.is_hidden() && !collection.projects.is_empty()) + if ((if hide_unlisted { + collection.status.is_searchable() + } else { + !collection.status.is_hidden() + }) && !collection.projects.is_empty()) || user_option.as_ref().is_some_and(|x| x.role.is_mod()) { return_collections.push(collection.into()); diff --git a/apps/labrinth/src/models/v3/collections.rs b/apps/labrinth/src/models/v3/collections.rs index 652a44d2..6bcdd538 100644 --- a/apps/labrinth/src/models/v3/collections.rs +++ b/apps/labrinth/src/models/v3/collections.rs @@ -92,7 +92,7 @@ impl CollectionStatus { } } - // Project pages + info cannot be viewed + // Collection pages + info cannot be viewed pub fn is_hidden(&self) -> bool { match self { CollectionStatus::Rejected => true, @@ -103,6 +103,11 @@ impl CollectionStatus { } } + // Collection can be displayed in on user page + pub fn is_searchable(&self) -> bool { + matches!(self, CollectionStatus::Listed) + } + pub fn is_approved(&self) -> bool { match self { CollectionStatus::Listed => true, diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index 6f795de9..ab8058e0 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -163,7 +163,8 @@ pub async fn collections_get( .ok(); let collections = - filter_visible_collections(collections_data, &user_option).await?; + filter_visible_collections(collections_data, &user_option, false) + .await?; Ok(HttpResponse::Ok().json(collections)) } @@ -192,7 +193,7 @@ pub async fn collection_get( .ok(); if let Some(data) = collection_data { - if is_visible_collection(&data, &user_option).await? { + if is_visible_collection(&data, &user_option, false).await? { return Ok(HttpResponse::Ok().json(Collection::from(data))); } } diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index d11c6b2d..9f48fc6b 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -1,14 +1,14 @@ use std::{collections::HashMap, sync::Arc}; use super::{ApiError, oauth_clients::get_user_clients}; -use crate::file_hosting::FileHostPublicity; -use crate::util::img::delete_old_images; use crate::{ - auth::{filter_visible_projects, get_user_from_headers}, + auth::{ + filter_visible_collections, filter_visible_projects, + get_user_from_headers, + }, database::{models::DBUser, redis::RedisPool}, - file_hosting::FileHost, + file_hosting::{FileHost, FileHostPublicity}, models::{ - collections::{Collection, CollectionStatus}, notifications::Notification, pats::Scopes, projects::Project, @@ -16,7 +16,7 @@ use crate::{ }, queue::session::AuthQueue, util::{ - routes::read_limited_from_payload, + img::delete_old_images, routes::read_limited_from_payload, validate::validation_errors_to_string, }, }; @@ -244,27 +244,19 @@ pub async fn collections_list( let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?; if let Some(id) = id_option.map(|x| x.id) { - let user_id: UserId = id.into(); - - let can_view_private = - user.is_some_and(|y| y.role.is_mod() || y.id == user_id); - - let project_data = DBUser::get_collections(id, &**pool).await?; + let collection_data = DBUser::get_collections(id, &**pool).await?; let response: Vec<_> = crate::database::models::DBCollection::get_many( - &project_data, + &collection_data, &**pool, &redis, ) - .await? - .into_iter() - .filter(|x| { - can_view_private || matches!(x.status, CollectionStatus::Listed) - }) - .map(Collection::from) - .collect(); + .await?; - Ok(HttpResponse::Ok().json(response)) + let collections = + filter_visible_collections(response, &user, true).await?; + + Ok(HttpResponse::Ok().json(collections)) } else { Err(ApiError::NotFound) } From 88044782216ec512633df4d554a3661703ca4d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= <7822554+AlexTMjugador@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:29:21 +0200 Subject: [PATCH 09/59] fix(frontend): hide subscription button in blog before sub status is determined (#4072) --- .../src/components/ui/NewsletterButton.vue | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/apps/frontend/src/components/ui/NewsletterButton.vue b/apps/frontend/src/components/ui/NewsletterButton.vue index 61778eaa..43d59b76 100644 --- a/apps/frontend/src/components/ui/NewsletterButton.vue +++ b/apps/frontend/src/components/ui/NewsletterButton.vue @@ -1,29 +1,28 @@ diff --git a/apps/frontend/src/components/ui/thread/ThreadMessage.vue b/apps/frontend/src/components/ui/thread/ThreadMessage.vue index 9d962c98..f47b0661 100644 --- a/apps/frontend/src/components/ui/thread/ThreadMessage.vue +++ b/apps/frontend/src/components/ui/thread/ThreadMessage.vue @@ -36,7 +36,7 @@ v-tooltip="'Modrinth Team'" /> diff --git a/apps/frontend/src/helpers/moderation.ts b/apps/frontend/src/helpers/moderation.ts new file mode 100644 index 00000000..0e842fef --- /dev/null +++ b/apps/frontend/src/helpers/moderation.ts @@ -0,0 +1,236 @@ +import type { ExtendedReport, OwnershipTarget } from "@modrinth/moderation"; +import type { + Thread, + Version, + User, + Project, + TeamMember, + Organization, + Report, +} from "@modrinth/utils"; + +export const useModerationCache = () => ({ + threads: useState>("moderation-report-cache-threads", () => new Map()), + users: useState>("moderation-report-cache-users", () => new Map()), + projects: useState>("moderation-report-cache-projects", () => new Map()), + versions: useState>("moderation-report-cache-versions", () => new Map()), + teams: useState>("moderation-report-cache-teams", () => new Map()), + orgs: useState>("moderation-report-cache-orgs", () => new Map()), +}); + +// TODO: @AlexTMjugador - backend should do all of these functions. +export async function enrichReportBatch(reports: Report[]): Promise { + if (reports.length === 0) return []; + + const cache = useModerationCache(); + + const threadIDs = reports + .map((r) => r.thread_id) + .filter(Boolean) + .filter((id) => !cache.threads.value.has(id)); + const userIDs = [ + ...reports.filter((r) => r.item_type === "user").map((r) => r.item_id), + ...reports.map((r) => r.reporter), + ].filter((id) => !cache.users.value.has(id)); + const versionIDs = reports + .filter((r) => r.item_type === "version") + .map((r) => r.item_id) + .filter((id) => !cache.versions.value.has(id)); + const projectIDs = reports + .filter((r) => r.item_type === "project") + .map((r) => r.item_id) + .filter((id) => !cache.projects.value.has(id)); + + const [newThreads, newVersions, newUsers] = await Promise.all([ + threadIDs.length > 0 + ? (fetchSegmented(threadIDs, (ids) => `threads?ids=${asEncodedJsonArray(ids)}`) as Promise< + Thread[] + >) + : Promise.resolve([]), + versionIDs.length > 0 + ? (fetchSegmented(versionIDs, (ids) => `versions?ids=${asEncodedJsonArray(ids)}`) as Promise< + Version[] + >) + : Promise.resolve([]), + [...new Set(userIDs)].length > 0 + ? (fetchSegmented( + [...new Set(userIDs)], + (ids) => `users?ids=${asEncodedJsonArray(ids)}`, + ) as Promise) + : Promise.resolve([]), + ]); + + newThreads.forEach((t) => cache.threads.value.set(t.id, t)); + newVersions.forEach((v) => cache.versions.value.set(v.id, v)); + newUsers.forEach((u) => cache.users.value.set(u.id, u)); + + const allVersions = [...newVersions, ...Array.from(cache.versions.value.values())]; + const fullProjectIds = new Set([ + ...projectIDs, + ...allVersions + .filter((v) => versionIDs.includes(v.id)) + .map((v) => v.project_id) + .filter(Boolean), + ]); + + const uncachedProjectIds = Array.from(fullProjectIds).filter( + (id) => !cache.projects.value.has(id), + ); + const newProjects = + uncachedProjectIds.length > 0 + ? ((await fetchSegmented( + uncachedProjectIds, + (ids) => `projects?ids=${asEncodedJsonArray(ids)}`, + )) as Project[]) + : []; + + newProjects.forEach((p) => cache.projects.value.set(p.id, p)); + + const allProjects = [...newProjects, ...Array.from(cache.projects.value.values())]; + const teamIds = [...new Set(allProjects.map((p) => p.team).filter(Boolean))].filter( + (id) => !cache.teams.value.has(id || "invalid team id"), + ); + const orgIds = [...new Set(allProjects.map((p) => p.organization).filter(Boolean))].filter( + (id) => !cache.orgs.value.has(id), + ); + + const [newTeams, newOrgs] = await Promise.all([ + teamIds.length > 0 + ? (fetchSegmented(teamIds, (ids) => `teams?ids=${asEncodedJsonArray(ids)}`) as Promise< + TeamMember[][] + >) + : Promise.resolve([]), + orgIds.length > 0 + ? (fetchSegmented(orgIds, (ids) => `organizations?ids=${asEncodedJsonArray(ids)}`, { + apiVersion: 3, + }) as Promise) + : Promise.resolve([]), + ]); + + newTeams.forEach((team) => { + if (team.length > 0) cache.teams.value.set(team[0].team_id, team); + }); + newOrgs.forEach((org) => cache.orgs.value.set(org.id, org)); + + return reports.map((report) => { + const thread = cache.threads.value.get(report.thread_id) || ({} as Thread); + const version = + report.item_type === "version" ? cache.versions.value.get(report.item_id) : undefined; + + const project = + report.item_type === "project" + ? cache.projects.value.get(report.item_id) + : report.item_type === "version" && version + ? cache.projects.value.get(version.project_id) + : undefined; + + let target: OwnershipTarget | undefined; + + if (report.item_type === "user") { + const targetUser = cache.users.value.get(report.item_id); + if (targetUser) { + target = { + name: targetUser.username, + slug: targetUser.username, + avatar_url: targetUser.avatar_url, + type: "user", + }; + } + } else if (project) { + let owner: TeamMember | null = null; + let org: Organization | null = null; + + if (project.team) { + const teamMembers = cache.teams.value.get(project.team); + if (teamMembers) { + owner = teamMembers.find((member) => member.role === "Owner") || null; + } + } + + if (project.organization) { + org = cache.orgs.value.get(project.organization) || null; + } + + if (org) { + target = { + name: org.name, + avatar_url: org.icon_url, + type: "organization", + slug: org.slug, + }; + } else if (owner) { + target = { + name: owner.user.username, + avatar_url: owner.user.avatar_url, + type: "user", + slug: owner.user.username, + }; + } + } + + return { + ...report, + thread, + reporter_user: cache.users.value.get(report.reporter) || ({} as User), + project, + user: report.item_type === "user" ? cache.users.value.get(report.item_id) : undefined, + version, + target, + }; + }); +} + +// Doesn't need to be in @modrinth/moderation because it is specific to the frontend. +export interface ModerationProject { + project: any; + owner: TeamMember | null; + org: Organization | null; +} + +export async function enrichProjectBatch(projects: any[]): Promise { + const teamIds = [...new Set(projects.map((p) => p.team_id).filter(Boolean))]; + const orgIds = [...new Set(projects.map((p) => p.organization).filter(Boolean))]; + + const [teamsData, orgsData]: [TeamMember[][], Organization[]] = await Promise.all([ + teamIds.length > 0 + ? fetchSegmented(teamIds, (ids) => `teams?ids=${asEncodedJsonArray(ids)}`) + : Promise.resolve([]), + orgIds.length > 0 + ? fetchSegmented(orgIds, (ids) => `organizations?ids=${asEncodedJsonArray(ids)}`, { + apiVersion: 3, + }) + : Promise.resolve([]), + ]); + + const cache = useModerationCache(); + + teamsData.forEach((team) => { + if (team.length > 0) cache.teams.value.set(team[0].team_id, team); + }); + + orgsData.forEach((org: Organization) => { + cache.orgs.value.set(org.id, org); + }); + + return projects.map((project) => { + let owner: TeamMember | null = null; + let org: Organization | null = null; + + if (project.team_id) { + const teamMembers = cache.teams.value.get(project.team_id); + if (teamMembers) { + owner = teamMembers.find((member) => member.role === "Owner") || null; + } + } + + if (project.organization) { + org = cache.orgs.value.get(project.organization) || null; + } + + return { + project, + owner, + org, + } as ModerationProject; + }); +} diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index 308b7e3a..0a118099 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -295,7 +295,7 @@ { id: 'review-projects', color: 'orange', - link: '/moderation/review', + link: '/moderation/', }, { id: 'review-reports', @@ -981,23 +981,6 @@ const userMenuOptions = computed(() => { }, ]; - if ( - (auth.value && auth.value.user && auth.value.user.role === "moderator") || - auth.value.user.role === "admin" - ) { - options = [ - ...options, - { - divider: true, - }, - { - id: "moderation", - color: "orange", - link: "/moderation/review", - }, - ]; - } - options = [ ...options, { diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index 38e72448..3a5bdd37 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -476,6 +476,30 @@ "layout.nav.search": { "message": "Search" }, + "moderation.filter.by": { + "message": "Filter by" + }, + "moderation.moderate": { + "message": "Moderate" + }, + "moderation.page.projects": { + "message": "Projects" + }, + "moderation.page.reports": { + "message": "Reports" + }, + "moderation.page.technicalReview": { + "message": "Technical Review" + }, + "moderation.search.placeholder": { + "message": "Search..." + }, + "moderation.sort.by": { + "message": "Sort by" + }, + "moderation.technical.search.placeholder": { + "message": "Search tech reviews..." + }, "profile.button.billing": { "message": "Manage user billing" }, diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[id].vue index 2bb35162..1dc0cfd6 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[id].vue @@ -689,7 +689,10 @@ }, { id: 'moderation-checklist', - action: () => (showModerationChecklist = true), + action: () => { + moderationStore.setSingleProject(project.id); + showModerationChecklist = true; + }, color: 'orange', hoverOnly: true, shown: @@ -870,19 +873,6 @@ @delete-version="deleteVersion" /> - -
- - -
@@ -890,9 +880,8 @@ v-if="auth.user && tags.staffRoles.includes(auth.user.role) && showModerationChecklist" class="moderation-checklist" > - { - console.log("Future project IDs updated:", newValue); -}); - watch( showModerationChecklist, (newValue) => { diff --git a/apps/frontend/src/pages/[type]/[id]/gallery.vue b/apps/frontend/src/pages/[type]/[id]/gallery.vue index 35eb72a2..7b5327ed 100644 --- a/apps/frontend/src/pages/[type]/[id]/gallery.vue +++ b/apps/frontend/src/pages/[type]/[id]/gallery.vue @@ -365,8 +365,10 @@ export default defineNuxtComponent({ if (e.key === "Escape") { this.expandedGalleryItem = null; } else if (e.key === "ArrowLeft") { + e.stopPropagation(); this.previousImage(); } else if (e.key === "ArrowRight") { + e.stopPropagation(); this.nextImage(); } } diff --git a/apps/frontend/src/pages/moderation.vue b/apps/frontend/src/pages/moderation.vue index 4dbb509d..3de15993 100644 --- a/apps/frontend/src/pages/moderation.vue +++ b/apps/frontend/src/pages/moderation.vue @@ -1,33 +1,84 @@ - diff --git a/apps/frontend/src/pages/moderation/index.vue b/apps/frontend/src/pages/moderation/index.vue index aa4ff5fa..c344d7cb 100644 --- a/apps/frontend/src/pages/moderation/index.vue +++ b/apps/frontend/src/pages/moderation/index.vue @@ -1,42 +1,339 @@ - diff --git a/apps/frontend/src/pages/moderation/report/[id].vue b/apps/frontend/src/pages/moderation/report/[id].vue deleted file mode 100644 index d485333d..00000000 --- a/apps/frontend/src/pages/moderation/report/[id].vue +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/apps/frontend/src/pages/moderation/reports.vue b/apps/frontend/src/pages/moderation/reports.vue deleted file mode 100644 index af15186e..00000000 --- a/apps/frontend/src/pages/moderation/reports.vue +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/apps/frontend/src/pages/moderation/reports/[id].vue b/apps/frontend/src/pages/moderation/reports/[id].vue new file mode 100644 index 00000000..8ec2c4d6 --- /dev/null +++ b/apps/frontend/src/pages/moderation/reports/[id].vue @@ -0,0 +1,28 @@ + + + diff --git a/apps/frontend/src/pages/moderation/reports/index.vue b/apps/frontend/src/pages/moderation/reports/index.vue new file mode 100644 index 00000000..4d375ffe --- /dev/null +++ b/apps/frontend/src/pages/moderation/reports/index.vue @@ -0,0 +1,290 @@ + + + diff --git a/apps/frontend/src/pages/moderation/review.vue b/apps/frontend/src/pages/moderation/review.vue deleted file mode 100644 index 0613982d..00000000 --- a/apps/frontend/src/pages/moderation/review.vue +++ /dev/null @@ -1,304 +0,0 @@ - - - - diff --git a/apps/frontend/src/pages/moderation/technical-review-mockup.vue b/apps/frontend/src/pages/moderation/technical-review-mockup.vue new file mode 100644 index 00000000..f18897fc --- /dev/null +++ b/apps/frontend/src/pages/moderation/technical-review-mockup.vue @@ -0,0 +1,386 @@ + + + diff --git a/apps/frontend/src/pages/moderation/technical-review.vue b/apps/frontend/src/pages/moderation/technical-review.vue new file mode 100644 index 00000000..40f28fec --- /dev/null +++ b/apps/frontend/src/pages/moderation/technical-review.vue @@ -0,0 +1,3 @@ + diff --git a/apps/frontend/src/store/moderation.ts b/apps/frontend/src/store/moderation.ts new file mode 100644 index 00000000..d89a68fc --- /dev/null +++ b/apps/frontend/src/store/moderation.ts @@ -0,0 +1,98 @@ +import { defineStore, createPinia } from "pinia"; +import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; + +export interface ModerationQueue { + items: string[]; + total: number; + completed: number; + skipped: number; + lastUpdated: Date; +} + +const EMPTY_QUEUE: Partial = { + items: [], + + // TODO: Consider some form of displaying this in the checklist, maybe at the end + total: 0, + completed: 0, + skipped: 0, +}; + +function createEmptyQueue(): ModerationQueue { + return { ...EMPTY_QUEUE, lastUpdated: new Date() } as ModerationQueue; +} + +const pinia = createPinia(); +pinia.use(piniaPluginPersistedstate); + +export const useModerationStore = defineStore("moderation", { + state: () => ({ + currentQueue: createEmptyQueue(), + }), + + getters: { + queueLength: (state) => state.currentQueue.items.length, + hasItems: (state) => state.currentQueue.items.length > 0, + progress: (state) => { + if (state.currentQueue.total === 0) return 0; + return (state.currentQueue.completed + state.currentQueue.skipped) / state.currentQueue.total; + }, + }, + + actions: { + setQueue(projectIDs: string[]) { + this.currentQueue = { + items: [...projectIDs], + total: projectIDs.length, + completed: 0, + skipped: 0, + lastUpdated: new Date(), + }; + }, + + setSingleProject(projectId: string) { + this.currentQueue = { + items: [projectId], + total: 1, + completed: 0, + skipped: 0, + lastUpdated: new Date(), + }; + }, + + completeCurrentProject(projectId: string, status: "completed" | "skipped" = "completed") { + if (status === "completed") { + this.currentQueue.completed++; + } else { + this.currentQueue.skipped++; + } + + this.currentQueue.items = this.currentQueue.items.filter((id: string) => id !== projectId); + this.currentQueue.lastUpdated = new Date(); + + return this.currentQueue.items.length > 0; + }, + + getCurrentProjectId(): string | null { + return this.currentQueue.items[0] || null; + }, + + resetQueue() { + this.currentQueue = createEmptyQueue(); + }, + }, + + persist: { + key: "moderation-store", + serializer: { + serialize: JSON.stringify, + deserialize: (value: string) => { + const parsed = JSON.parse(value); + if (parsed.currentQueue?.lastUpdated) { + parsed.currentQueue.lastUpdated = new Date(parsed.currentQueue.lastUpdated); + } + return parsed; + }, + }, + }, +}); diff --git a/packages/assets/generated-icons.ts b/packages/assets/generated-icons.ts index 249a53b6..f0104415 100644 --- a/packages/assets/generated-icons.ts +++ b/packages/assets/generated-icons.ts @@ -38,6 +38,7 @@ import _CodeIcon from './icons/code.svg?component' import _CoffeeIcon from './icons/coffee.svg?component' import _CogIcon from './icons/cog.svg?component' import _CoinsIcon from './icons/coins.svg?component' +import _CollapseIcon from './icons/collapse.svg?component' import _CollectionIcon from './icons/collection.svg?component' import _CompassIcon from './icons/compass.svg?component' import _ContractIcon from './icons/contract.svg?component' @@ -52,6 +53,7 @@ import _DatabaseIcon from './icons/database.svg?component' import _DownloadIcon from './icons/download.svg?component' import _DropdownIcon from './icons/dropdown.svg?component' import _EditIcon from './icons/edit.svg?component' +import _EllipsisVerticalIcon from './icons/ellipsis-vertical.svg?component' import _ExpandIcon from './icons/expand.svg?component' import _ExternalIcon from './icons/external.svg?component' import _EyeOffIcon from './icons/eye-off.svg?component' @@ -229,6 +231,7 @@ export const CodeIcon = _CodeIcon export const CoffeeIcon = _CoffeeIcon export const CogIcon = _CogIcon export const CoinsIcon = _CoinsIcon +export const CollapseIcon = _CollapseIcon export const CollectionIcon = _CollectionIcon export const CompassIcon = _CompassIcon export const ContractIcon = _ContractIcon @@ -243,6 +246,7 @@ export const DatabaseIcon = _DatabaseIcon export const DownloadIcon = _DownloadIcon export const DropdownIcon = _DropdownIcon export const EditIcon = _EditIcon +export const EllipsisVerticalIcon = _EllipsisVerticalIcon export const ExpandIcon = _ExpandIcon export const ExternalIcon = _ExternalIcon export const EyeOffIcon = _EyeOffIcon diff --git a/packages/assets/icons/collapse.svg b/packages/assets/icons/collapse.svg new file mode 100644 index 00000000..49723c69 --- /dev/null +++ b/packages/assets/icons/collapse.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/packages/assets/icons/ellipsis-vertical.svg b/packages/assets/icons/ellipsis-vertical.svg new file mode 100644 index 00000000..ebe30683 --- /dev/null +++ b/packages/assets/icons/ellipsis-vertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/moderation/data/messages/reports/antivirus.md b/packages/moderation/data/messages/reports/antivirus.md new file mode 100644 index 00000000..338dd8d6 --- /dev/null +++ b/packages/moderation/data/messages/reports/antivirus.md @@ -0,0 +1,3 @@ +Unfortunately, anti-virus software has consistently been found to be an unreliable tool for Minecraft mods. + +If you have evidence of malicious activity concerning a specific mod, or of malicious code decompiled from a mod on Modrinth, please create a new Report and provide the required details, thank you. diff --git a/packages/moderation/data/messages/reports/confirmed-malware.md b/packages/moderation/data/messages/reports/confirmed-malware.md new file mode 100644 index 00000000..5f7cc2a5 --- /dev/null +++ b/packages/moderation/data/messages/reports/confirmed-malware.md @@ -0,0 +1,3 @@ +Thank you for your report. + +This project was confirmed to be malicious after a detailed investigation. Luckily, thanks to your report and quick action from our team, we have reason to believe this did not impact a significant amount of users and we have taken precautions to prevent this malicious code from appearing on Modrinth again. diff --git a/packages/moderation/data/messages/reports/gameplay-issue.md b/packages/moderation/data/messages/reports/gameplay-issue.md new file mode 100644 index 00000000..ee0c9dd9 --- /dev/null +++ b/packages/moderation/data/messages/reports/gameplay-issue.md @@ -0,0 +1,6 @@ +Unfortunately, the Moderation team is unable to assist with your issue. + +The reporting system is exclusively for reporting issues to Moderation staff; only violations of [Modrinth's Content Rules](https://modrinth.com/legal/rules) should be reported. The members of the project you're reporting do not see that you have submitted a report. + +If you are having issues with crashes, please check out [our FAQ section](https://support.modrinth.com/aen/articles/8792916) to learn how to diagnose and fix crashes. +For other project-specific issues consider asking the project's own community, check for a Discord or Issues link on the project page. diff --git a/packages/moderation/data/messages/reports/platform-issue.md b/packages/moderation/data/messages/reports/platform-issue.md new file mode 100644 index 00000000..a6bcd943 --- /dev/null +++ b/packages/moderation/data/messages/reports/platform-issue.md @@ -0,0 +1,5 @@ +Unfortunately, the Moderation team is unable to assist with your issue. + +The reporting system is exclusively for reporting issues to Moderation staff; only violations of [Modrinth's Content Rules](https://modrinth.com/legal/rules) should be reported. + +Please reach out to the [Modrinth Help Center](https://support.modrinth.com/) so we can better assist you and bring up your concerns with our platform tean, diff --git a/packages/moderation/data/messages/reports/spam.md b/packages/moderation/data/messages/reports/spam.md new file mode 100644 index 00000000..876dda2b --- /dev/null +++ b/packages/moderation/data/messages/reports/spam.md @@ -0,0 +1,3 @@ +The reporting system is exclusively for reporting issues to Modrinth staff; only violations of [Modrinth's Content Rules](https://modrinth.com/legal/rules) should be reported. The members of the project you're reporting do not see that you have submitted a report. + +Please ensure you are using the Reports system appropriately, repeated misuse may result in account suspension. diff --git a/packages/moderation/data/messages/reports/stale.md b/packages/moderation/data/messages/reports/stale.md new file mode 100644 index 00000000..cc21f5f1 --- /dev/null +++ b/packages/moderation/data/messages/reports/stale.md @@ -0,0 +1,3 @@ +We haven't received a response in some time, so we're closing this report thread. + +If you have additional information to share we ask that you create a new report. diff --git a/packages/moderation/data/report-quick-replies.ts b/packages/moderation/data/report-quick-replies.ts new file mode 100644 index 00000000..db1fd232 --- /dev/null +++ b/packages/moderation/data/report-quick-replies.ts @@ -0,0 +1,34 @@ +import type { ReportQuickReply } from '../types/reports' + +export default [ + { + label: 'Antivirus', + message: async () => (await import('./messages/reports/antivirus.md?raw')).default, + private: false, + }, + { + label: 'Spam', + message: async () => (await import('./messages/reports/spam.md?raw')).default, + private: false, + }, + { + label: 'Gameplay Issue', + message: async () => (await import('./messages/reports/gameplay-issue.md?raw')).default, + private: false, + }, + { + label: 'Platform Issue', + message: async () => (await import('./messages/reports/platform-issue.md?raw')).default, + private: false, + }, + { + label: 'Stale', + message: async () => (await import('./messages/reports/stale.md?raw')).default, + private: false, + }, + { + label: 'Confirmed Malware', + message: async () => (await import('./messages/reports/confirmed-malware.md?raw')).default, + private: false, + }, +] as ReadonlyArray diff --git a/packages/moderation/data/stages/versions.ts b/packages/moderation/data/stages/versions.ts index 9976fa96..7f5ca33a 100644 --- a/packages/moderation/data/stages/versions.ts +++ b/packages/moderation/data/stages/versions.ts @@ -68,7 +68,7 @@ const versions: Stage = { message: async () => '', enablesActions: [ { - id: 'versions_incorrect_project_type_options', + id: 'versions_alternate_versions_options', type: 'dropdown', label: 'How are the alternate versions distributed?', options: [ diff --git a/packages/moderation/index.ts b/packages/moderation/index.ts index a7cebdcd..0ee0afdf 100644 --- a/packages/moderation/index.ts +++ b/packages/moderation/index.ts @@ -2,8 +2,10 @@ export * from './types/actions' export * from './types/messages' export * from './types/stage' export * from './types/keybinds' +export * from './types/reports' export * from './utils' -export { finalPermissionMessages } from './data/modpack-permissions-stage' +export { finalPermissionMessages } from './data/modpack-permissions-stage' export { default as checklist } from './data/checklist' export { default as keybinds } from './data/keybinds' +export { default as reportQuickReplies } from './data/report-quick-replies' diff --git a/packages/moderation/types/reports.ts b/packages/moderation/types/reports.ts new file mode 100644 index 00000000..c05dc348 --- /dev/null +++ b/packages/moderation/types/reports.ts @@ -0,0 +1,28 @@ +import type { Project, Report, Thread, User, Version, DelphiReport } from '@modrinth/utils' + +export interface OwnershipTarget { + name: string + slug: string + avatar_url?: string + type: 'user' | 'organization' +} + +export interface ExtendedReport extends Report { + thread: Thread + reporter_user: User + project?: Project + user?: User + version?: Version + target?: OwnershipTarget +} + +export interface ExtendedDelphiReport extends DelphiReport { + target?: OwnershipTarget +} + +export interface ReportQuickReply { + label: string + message: string | ((report: ExtendedReport) => Promise | string) + shouldShow?: (report: ExtendedReport) => boolean + private?: boolean +} diff --git a/packages/ui/src/components/base/CollapsibleRegion.vue b/packages/ui/src/components/base/CollapsibleRegion.vue new file mode 100644 index 00000000..14d06c83 --- /dev/null +++ b/packages/ui/src/components/base/CollapsibleRegion.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/packages/ui/src/components/base/DropdownSelect.vue b/packages/ui/src/components/base/DropdownSelect.vue index c87ef340..9843eb15 100644 --- a/packages/ui/src/components/base/DropdownSelect.vue +++ b/packages/ui/src/components/base/DropdownSelect.vue @@ -163,7 +163,6 @@ const onFocus = () => { } const onBlur = (event) => { - console.log(event) if (!isChildOfDropdown(event.relatedTarget)) { dropdownVisible.value = false } diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index e1217fc5..85b7f2da 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -10,6 +10,7 @@ export { default as Card } from './base/Card.vue' export { default as Checkbox } from './base/Checkbox.vue' export { default as Chips } from './base/Chips.vue' export { default as Collapsible } from './base/Collapsible.vue' +export { default as CollapsibleRegion } from './base/CollapsibleRegion.vue' export { default as ContentPageHeader } from './base/ContentPageHeader.vue' export { default as CopyCode } from './base/CopyCode.vue' export { default as DoubleIcon } from './base/DoubleIcon.vue' diff --git a/packages/utils/types.ts b/packages/utils/types.ts index a56bc3c5..61f553a2 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -18,7 +18,7 @@ export type DonationPlatform = | { short: 'ko-fi'; name: 'Ko-fi' } | { short: 'other'; name: 'Other' } -export type ProjectType = 'mod' | 'modpack' | 'resourcepack' | 'shader' +export type ProjectType = 'mod' | 'modpack' | 'resourcepack' | 'shader' | 'plugin' | 'datapack' export type MonetizationStatus = 'monetized' | 'demonetized' | 'force-demonetized' export type GameVersion = string @@ -65,7 +65,8 @@ export interface Project { client_side: Environment server_side: Environment - team: ModrinthId + team?: ModrinthId + team_id: ModrinthId thread_id: ModrinthId organization: ModrinthId @@ -76,6 +77,7 @@ export interface Project { donation_urls: DonationLink[] published: string + created?: string updated: string approved: string queued: string @@ -295,6 +297,60 @@ export type Report = { body: string } +// Threads +export interface Thread { + id: string + type: ThreadType + project_id: string | null + report_id: string | null + messages: ThreadMessage[] + members: User[] +} + +export type ThreadType = 'project' | 'report' | 'direct_message' + +export interface ThreadMessage { + id: string | null + author_id: string | null + body: MessageBody + created: string + hide_identity: boolean +} + +export type MessageBody = + | TextMessageBody + | StatusChangeMessageBody + | ThreadClosureMessageBody + | ThreadReopenMessageBody + | DeletedMessageBody + +export interface TextMessageBody { + type: 'text' + body: string + private: boolean + replying_to: string | null + associated_images: string[] +} + +export interface StatusChangeMessageBody { + type: 'status_change' + new_status: ProjectStatus + old_status: ProjectStatus +} + +export interface ThreadClosureMessageBody { + type: 'thread_closure' +} + +export interface ThreadReopenMessageBody { + type: 'thread_reopen' +} + +export interface DeletedMessageBody { + type: 'deleted' + private: boolean +} + // Moderation export interface ModerationModpackPermissionApprovalType { id: @@ -379,3 +435,38 @@ export interface ModerationJudgement { export interface ModerationJudgements { [sha1: string]: ModerationJudgement } + +// Delphi +export interface DelphiReport { + id: string + project: Project + version: Version + priority_score: number + detected_at: string + trace_type: + | 'reflection_indirection' + | 'xor_obfuscation' + | 'included_libraries' + | 'suspicious_binaries' + | 'corrupt_classes' + | 'suspicious_classes' + | 'url_usage' + | 'classloader_usage' + | 'processbuilder_usage' + | 'runtime_exec_usage' + | 'jni_usage' + | 'main_method' + | 'native_loading' + | 'malformed_jar' + | 'nested_jar_too_deep' + | 'failed_decompilation' + | 'analysis_failure' + | 'malware_easyforme' + | 'malware_simplyloader' + file_path: string + // pending = not reviewed yet. + // approved = approved as malicious, removed from modrinth + // rejected = not approved as malicious, remains on modrinth? + status: 'pending' | 'approved' | 'rejected' + content?: string +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a37c9043..c311ab04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,31 +125,31 @@ importers: devDependencies: '@eslint/compat': specifier: ^1.1.1 - version: 1.2.1(eslint@9.13.0(jiti@2.4.2)) + version: 1.2.1(eslint@9.13.0(jiti@2.5.1)) '@formatjs/cli': specifier: ^6.2.12 version: 6.2.12(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4)) '@nuxt/eslint-config': specifier: ^0.5.6 - version: 0.5.7(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + version: 0.5.7(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@taijased/vue-render-tracker': specifier: ^1.0.7 version: 1.0.7(vue@3.5.13(typescript@5.5.4)) '@vitejs/plugin-vue': specifier: ^5.0.4 - version: 5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) + version: 5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) autoprefixer: specifier: ^10.4.19 version: 10.4.20(postcss@8.4.49) eslint: specifier: ^9.9.1 - version: 9.13.0(jiti@2.4.2) + version: 9.13.0(jiti@2.5.1) eslint-config-custom: specifier: workspace:* version: link:../../packages/eslint-config-custom eslint-plugin-turbo: specifier: ^2.5.4 - version: 2.5.4(eslint@9.13.0(jiti@2.4.2))(turbo@2.5.4) + version: 2.5.4(eslint@9.13.0(jiti@2.5.1))(turbo@2.5.4) postcss: specifier: ^8.4.39 version: 8.4.49 @@ -170,7 +170,7 @@ importers: version: 5.5.4 vite: specifier: ^5.4.6 - version: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0) + version: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1) vue-tsc: specifier: ^2.1.6 version: 2.1.6(typescript@5.5.4) @@ -190,19 +190,19 @@ importers: version: 0.9.4(prettier@3.6.2)(typescript@5.8.2) '@astrojs/starlight': specifier: ^0.32.2 - version: 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)) + version: 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)) '@modrinth/assets': specifier: workspace:* version: link:../../packages/assets astro: specifier: ^5.4.1 - version: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0) + version: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0) sharp: specifier: ^0.33.5 version: 0.33.5 starlight-openapi: specifier: ^0.14.0 - version: 0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)))(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0))(openapi-types@12.1.3) + version: 0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)))(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0))(openapi-types@12.1.3) typescript: specifier: ^5.8.2 version: 5.8.2 @@ -284,6 +284,9 @@ importers: pinia: specifier: ^2.1.7 version: 2.1.7(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)) + pinia-plugin-persistedstate: + specifier: ^4.4.1 + version: 4.4.1(@nuxt/kit@3.17.5(magicast@0.3.5))(@pinia/nuxt@0.5.1(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)))(pinia@2.1.7(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))) prettier: specifier: ^3.6.2 version: 3.6.2 @@ -296,6 +299,9 @@ importers: three: specifier: ^0.172.0 version: 0.172.0 + vue-confetti-explosion: + specifier: ^1.0.2 + version: 1.0.2(vue@3.5.13(typescript@5.5.4)) vue-multiselect: specifier: 3.0.0-alpha.2 version: 3.0.0-alpha.2 @@ -317,7 +323,7 @@ importers: version: 6.2.12(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4)) '@nuxt/devtools': specifier: ^1.3.3 - version: 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) + version: 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) '@types/dompurify': specifier: ^3.0.5 version: 3.0.5 @@ -332,7 +338,7 @@ importers: version: 3.0.1(@formatjs/intl@2.10.4(typescript@5.5.4)) '@vintl/nuxt': specifier: ^1.9.2 - version: 1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) + version: 1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) autoprefixer: specifier: ^10.4.19 version: 10.4.20(postcss@8.4.49) @@ -344,7 +350,7 @@ importers: version: 10.4.2 nuxt: specifier: ^3.14.1592 - version: 3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4))(xml2js@0.6.2) + version: 3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.43.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue-tsc@2.1.6(typescript@5.5.4))(xml2js@0.6.2) postcss: specifier: ^8.4.39 version: 8.4.49 @@ -441,22 +447,22 @@ importers: devDependencies: '@nuxtjs/eslint-config-typescript': specifier: ^12.1.0 - version: 12.1.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + version: 12.1.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@vue/eslint-config-typescript': specifier: ^13.0.0 - version: 13.0.0(eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + version: 13.0.0(eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.13.0(jiti@2.4.2)) + version: 9.1.0(eslint@9.13.0(jiti@2.5.1)) eslint-config-turbo: specifier: ^2.0.7 - version: 2.0.7(eslint@9.13.0(jiti@2.4.2)) + version: 2.0.7(eslint@9.13.0(jiti@2.5.1)) eslint-plugin-prettier: specifier: ^5.2.1 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2))(prettier@3.6.2) + version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1))(prettier@3.6.2) eslint-plugin-unicorn: specifier: ^54.0.0 - version: 54.0.0(eslint@9.13.0(jiti@2.4.2)) + version: 54.0.0(eslint@9.13.0(jiti@2.5.1)) typescript: specifier: ^5.5.3 version: 5.5.4 @@ -4003,6 +4009,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deep-pick-omit@1.2.1: + resolution: {integrity: sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -6382,6 +6391,20 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pinia-plugin-persistedstate@4.4.1: + resolution: {integrity: sha512-lmuMPpXla2zJKjxEq34e1E9P9jxkWEhcVwwioCCE0izG45kkTOvQfCzvwhW3i38cvnaWC7T1eRdkd15Re59ldw==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + pinia@2.1.7: resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} peerDependencies: @@ -8273,6 +8296,12 @@ packages: vue-bundle-renderer@2.1.1: resolution: {integrity: sha512-+qALLI5cQncuetYOXp4yScwYvqh8c6SMXee3B+M7oTZxOgtESP0l4j/fXdEJoZ+EdMxkGWIj+aSEyjXkOdmd7g==} + vue-confetti-explosion@1.0.2: + resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==} + engines: {node: '>=12'} + peerDependencies: + vue: ^3.0.5 + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -8659,12 +8688,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0))': + '@astrojs/mdx@4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.2.0 '@mdx-js/mdx': 3.1.0(acorn@8.14.0) acorn: 8.14.0 - astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0) + astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0) es-module-lexer: 1.6.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -8688,16 +8717,16 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0))': + '@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0))': dependencies: - '@astrojs/mdx': 4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)) + '@astrojs/mdx': 4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)) '@astrojs/sitemap': 3.2.1 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0) - astro-expressive-code: 0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)) + astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0) + astro-expressive-code: 0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)) bcp-47: 2.1.0 hast-util-from-html: 2.0.2 hast-util-select: 6.0.2 @@ -9340,16 +9369,16 @@ snapshots: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.4.0(eslint@9.13.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.4.0(eslint@9.13.0(jiti@2.5.1))': dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.0': {} - '@eslint/compat@1.2.1(eslint@9.13.0(jiti@2.4.2))': + '@eslint/compat@1.2.1(eslint@9.13.0(jiti@2.5.1))': optionalDependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) '@eslint/config-array@0.18.0': dependencies: @@ -9799,12 +9828,12 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))': + '@nuxt/devtools-kit@1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) execa: 7.2.0 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) transitivePeerDependencies: - magicast - rollup @@ -9823,13 +9852,13 @@ snapshots: rc9: 2.1.2 semver: 7.7.1 - '@nuxt/devtools@1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))': + '@nuxt/devtools@1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))': dependencies: '@antfu/utils': 0.7.10 - '@nuxt/devtools-kit': 1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)) + '@nuxt/devtools-kit': 1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)) '@nuxt/devtools-wizard': 1.6.3 '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) - '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) + '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) '@vue/devtools-kit': 7.6.4 birpc: 0.2.19 consola: 3.2.3 @@ -9858,9 +9887,9 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 unimport: 3.14.4(rollup@4.28.1) - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)) - vite-plugin-vue-inspector: 5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)) + vite-plugin-vue-inspector: 5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)) which: 3.0.1 ws: 8.18.0 transitivePeerDependencies: @@ -9870,34 +9899,34 @@ snapshots: - utf-8-validate - vue - '@nuxt/eslint-config@0.5.7(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@nuxt/eslint-config@0.5.7(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@eslint/js': 9.13.0 - '@nuxt/eslint-plugin': 0.5.7(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@stylistic/eslint-plugin': 2.9.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/eslint-plugin': 8.10.0(@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/parser': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) - eslint-config-flat-gitignore: 0.3.0(eslint@9.13.0(jiti@2.4.2)) + '@nuxt/eslint-plugin': 0.5.7(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@stylistic/eslint-plugin': 2.9.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.10.0(@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/parser': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) + eslint-config-flat-gitignore: 0.3.0(eslint@9.13.0(jiti@2.5.1)) eslint-flat-config-utils: 0.4.0 - eslint-plugin-import-x: 4.3.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint-plugin-jsdoc: 50.4.3(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-regexp: 2.6.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-unicorn: 55.0.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.4.2)) + eslint-plugin-import-x: 4.3.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint-plugin-jsdoc: 50.4.3(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-regexp: 2.6.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-unicorn: 55.0.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.5.1)) globals: 15.11.0 local-pkg: 0.5.1 pathe: 1.1.2 - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.4.2)) + vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - typescript - '@nuxt/eslint-plugin@0.5.7(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@nuxt/eslint-plugin@0.5.7(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/types': 8.10.0 - '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) transitivePeerDependencies: - supports-color - typescript @@ -10002,12 +10031,12 @@ snapshots: - rollup - supports-color - '@nuxt/vite-builder@3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))': + '@nuxt/vite-builder@3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.43.1)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) '@rollup/plugin-replace': 6.0.1(rollup@4.28.1) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) autoprefixer: 10.4.20(postcss@8.4.49) clear: 0.1.0 consola: 3.2.3 @@ -10034,9 +10063,9 @@ snapshots: ufo: 1.5.4 unenv: 1.10.0 unplugin: 1.16.0 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) - vite-node: 2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) - vite-plugin-checker: 0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4)) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) + vite-node: 2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) + vite-plugin-checker: 0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue-tsc@2.1.6(typescript@5.5.4)) vue: 3.5.13(typescript@5.5.4) vue-bundle-renderer: 2.1.1 transitivePeerDependencies: @@ -10061,31 +10090,31 @@ snapshots: - vti - vue-tsc - '@nuxtjs/eslint-config-typescript@12.1.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@nuxtjs/eslint-config-typescript@12.1.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2)) - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.4.2)) + '@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1)) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.5.1)) transitivePeerDependencies: - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - typescript - '@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2))': + '@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1))': dependencies: - eslint: 9.13.0(jiti@2.4.2) - eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.2)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-node: 11.1.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-unicorn: 44.0.2(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.5.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-node: 11.1.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-unicorn: 44.0.2(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.5.1)) local-pkg: 0.4.3 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -10535,10 +10564,10 @@ snapshots: '@stripe/stripe-js@7.3.1': {} - '@stylistic/eslint-plugin@2.9.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@stylistic/eslint-plugin@2.9.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) eslint-visitor-keys: 4.1.0 espree: 10.2.0 estraverse: 5.3.0 @@ -10805,16 +10834,16 @@ snapshots: dependencies: '@types/node': 20.14.11 - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/utils': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/type-utils': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/utils': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -10825,15 +10854,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/type-utils': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/utils': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/type-utils': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/utils': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.16.1 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -10843,15 +10872,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.10.0(@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.10.0(@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/scope-manager': 8.10.0 - '@typescript-eslint/type-utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/type-utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) '@typescript-eslint/visitor-keys': 8.10.0 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -10861,40 +10890,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.16.1 debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/parser@8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 8.10.0 '@typescript-eslint/types': 8.10.0 '@typescript-eslint/typescript-estree': 8.10.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 8.10.0 debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -10915,34 +10944,34 @@ snapshots: '@typescript-eslint/types': 8.10.0 '@typescript-eslint/visitor-keys': 8.10.0 - '@typescript-eslint/type-utils@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/type-utils@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.4) - '@typescript-eslint/utils': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) - '@typescript-eslint/utils': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/typescript-estree': 8.10.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) debug: 4.4.0(supports-color@9.4.0) ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: @@ -11002,38 +11031,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/utils@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/utils@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@typescript-eslint/utils@8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@typescript-eslint/scope-manager': 8.10.0 '@typescript-eslint/types': 8.10.0 '@typescript-eslint/typescript-estree': 8.10.0(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) transitivePeerDependencies: - supports-color - typescript @@ -11114,12 +11143,12 @@ snapshots: '@formatjs/intl': 2.10.4(typescript@5.5.4) intl-messageformat: 10.5.14 - '@vintl/nuxt@1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': + '@vintl/nuxt@1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': dependencies: '@formatjs/intl': 2.10.4(typescript@5.5.4) '@formatjs/intl-localematcher': 0.5.4 '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) - '@vintl/unplugin': 2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) + '@vintl/unplugin': 2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1) '@vintl/vintl': 4.4.1(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)) astring: 1.8.6 consola: 3.2.3 @@ -11170,7 +11199,7 @@ snapshots: - ts-jest - vue - '@vintl/unplugin@2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': + '@vintl/unplugin@2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)': dependencies: '@formatjs/cli-lib': 6.4.2(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4)) '@formatjs/icu-messageformat-parser': 2.7.8 @@ -11181,7 +11210,7 @@ snapshots: unplugin: 1.16.0 optionalDependencies: rollup: 4.28.1 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) webpack: 5.92.1 transitivePeerDependencies: - '@glimmer/env' @@ -11205,24 +11234,24 @@ snapshots: transitivePeerDependencies: - typescript - '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0) '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0) - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) vue: 3.5.13(typescript@5.5.4) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))': + '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))': dependencies: - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) vue: 3.5.13(typescript@5.5.4) - '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))': + '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))': dependencies: - vite: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1) vue: 3.5.13(typescript@5.5.4) '@volar/kit@2.4.11(typescript@5.8.2)': @@ -11367,14 +11396,14 @@ snapshots: '@vue/devtools-api@6.6.4': {} - '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))': + '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4))': dependencies: '@vue/devtools-kit': 7.6.4 '@vue/devtools-shared': 7.6.7 mitt: 3.0.1 nanoid: 3.3.7 pathe: 1.1.2 - vite-hot-client: 0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)) + vite-hot-client: 0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)) vue: 3.5.13(typescript@5.5.4) transitivePeerDependencies: - vite @@ -11393,13 +11422,13 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4)': + '@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4)': dependencies: - '@typescript-eslint/eslint-plugin': 7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) - eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.4.2)) - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.4.2)) + '@typescript-eslint/eslint-plugin': 7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) + eslint-plugin-vue: 9.29.0(eslint@9.13.0(jiti@2.5.1)) + vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.5.1)) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -11823,12 +11852,12 @@ snapshots: astring@1.8.6: {} - astro-expressive-code@0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)): + astro-expressive-code@0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)): dependencies: - astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0) + astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0) rehype-expressive-code: 0.40.2 - astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0): + astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0): dependencies: '@astrojs/compiler': 2.10.4 '@astrojs/internal-helpers': 0.6.0 @@ -11880,8 +11909,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.15.0(db0@0.3.2) vfile: 6.0.3 - vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.2)(sass@1.77.6)(terser@5.42.0)(yaml@2.8.0) - vitefu: 1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.2)(sass@1.77.6)(terser@5.42.0)(yaml@2.8.0)) + vite: 6.2.0(@types/node@22.4.1)(jiti@2.5.1)(sass@1.77.6)(terser@5.43.1)(yaml@2.8.0) + vitefu: 1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.5.1)(sass@1.77.6)(terser@5.43.1)(yaml@2.8.0)) which-pm: 3.0.1 xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -12457,6 +12486,8 @@ snapshots: deep-is@0.1.4: {} + deep-pick-omit@1.2.1: {} + deepmerge@4.3.1: {} default-browser-id@5.0.0: {} @@ -12494,8 +12525,7 @@ snapshots: destr@2.0.3: {} - destr@2.0.5: - optional: true + destr@2.0.5: {} destroy@1.2.0: {} @@ -12857,27 +12887,27 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-flat-gitignore@0.3.0(eslint@9.13.0(jiti@2.4.2)): + eslint-config-flat-gitignore@0.3.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - '@eslint/compat': 1.2.1(eslint@9.13.0(jiti@2.4.2)) - eslint: 9.13.0(jiti@2.4.2) + '@eslint/compat': 1.2.1(eslint@9.13.0(jiti@2.5.1)) + eslint: 9.13.0(jiti@2.5.1) find-up-simple: 1.0.0 - eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.2)): + eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) - eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.2)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2)): + eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.5.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.5.1)) - eslint-config-turbo@2.0.7(eslint@9.13.0(jiti@2.4.2)): + eslint-config-turbo@2.0.7(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) - eslint-plugin-turbo: 2.0.7(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-plugin-turbo: 2.0.7(eslint@9.13.0(jiti@2.5.1)) eslint-flat-config-utils@0.4.0: dependencies: @@ -12891,13 +12921,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.5.1)): dependencies: debug: 4.4.0(supports-color@9.4.0) enhanced-resolve: 5.17.1 - eslint: 9.13.0(jiti@2.4.2) - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1)) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.15.0 @@ -12908,45 +12938,45 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2)): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.2)): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) - eslint: 9.13.0(jiti@2.4.2) + '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) + eslint: 9.13.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-es@3.0.1(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-es@3.0.1(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-es@4.1.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-es@4.1.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4): + eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4): dependencies: - '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 8.10.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) debug: 4.4.0(supports-color@9.4.0) doctrine: 3.0.0 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 get-tsconfig: 4.7.5 is-glob: 4.0.3 @@ -12958,7 +12988,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.5.1)): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -12966,9 +12996,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.2)) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -12979,13 +13009,13 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.5.1)): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -12993,9 +13023,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.2)) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -13006,20 +13036,20 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 7.16.1(eslint@9.13.0(jiti@2.5.1))(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsdoc@50.4.3(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-jsdoc@50.4.3(eslint@9.13.0(jiti@2.5.1)): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 debug: 4.4.0(supports-color@9.4.0) escape-string-regexp: 4.0.0 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) espree: 10.2.0 esquery: 1.6.0 parse-imports: 2.2.1 @@ -13029,71 +13059,71 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.5.1)): dependencies: builtins: 5.1.0 - eslint: 9.13.0(jiti@2.4.2) - eslint-plugin-es: 4.1.0(eslint@9.13.0(jiti@2.4.2)) - eslint-utils: 3.0.0(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-plugin-es: 4.1.0(eslint@9.13.0(jiti@2.5.1)) + eslint-utils: 3.0.0(eslint@9.13.0(jiti@2.5.1)) ignore: 5.3.1 is-core-module: 2.15.0 minimatch: 3.1.2 resolve: 1.22.8 semver: 7.7.1 - eslint-plugin-node@11.1.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-node@11.1.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) - eslint-plugin-es: 3.0.1(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-plugin-es: 3.0.1(eslint@9.13.0(jiti@2.5.1)) eslint-utils: 2.1.0 ignore: 5.3.1 minimatch: 3.1.2 resolve: 1.22.8 semver: 6.3.1 - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.2)))(eslint@9.13.0(jiti@2.4.2))(prettier@3.6.2): + eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.5.1)))(eslint@9.13.0(jiti@2.5.1))(prettier@3.6.2): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@9.13.0(jiti@2.4.2)) + eslint-config-prettier: 9.1.0(eslint@9.13.0(jiti@2.5.1)) - eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) - eslint-plugin-regexp@2.6.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-regexp@2.6.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.11.0 comment-parser: 1.4.1 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) jsdoc-type-pratt-parser: 4.1.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-turbo@2.0.7(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-turbo@2.0.7(eslint@9.13.0(jiti@2.5.1)): dependencies: dotenv: 16.0.3 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) - eslint-plugin-turbo@2.5.4(eslint@9.13.0(jiti@2.4.2))(turbo@2.5.4): + eslint-plugin-turbo@2.5.4(eslint@9.13.0(jiti@2.5.1))(turbo@2.5.4): dependencies: dotenv: 16.0.3 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) turbo: 2.5.4 - eslint-plugin-unicorn@44.0.2(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-unicorn@44.0.2(eslint@9.13.0(jiti@2.5.1)): dependencies: '@babel/helper-validator-identifier': 7.25.9 ci-info: 3.9.0 clean-regexp: 1.0.0 - eslint: 9.13.0(jiti@2.4.2) - eslint-utils: 3.0.0(eslint@9.13.0(jiti@2.4.2)) + eslint: 9.13.0(jiti@2.5.1) + eslint-utils: 3.0.0(eslint@9.13.0(jiti@2.5.1)) esquery: 1.6.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -13105,15 +13135,15 @@ snapshots: semver: 7.7.1 strip-indent: 3.0.0 - eslint-plugin-unicorn@54.0.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-unicorn@54.0.0(eslint@9.13.0(jiti@2.5.1)): dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@eslint/eslintrc': 3.1.0 ci-info: 4.0.0 clean-regexp: 1.0.0 core-js-compat: 3.37.1 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) esquery: 1.6.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -13127,14 +13157,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@55.0.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-unicorn@55.0.0(eslint@9.13.0(jiti@2.5.1)): dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) ci-info: 4.0.0 clean-regexp: 1.0.0 core-js-compat: 3.37.1 - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) esquery: 1.6.0 globals: 15.11.0 indent-string: 4.0.0 @@ -13147,16 +13177,16 @@ snapshots: semver: 7.6.3 strip-indent: 3.0.0 - eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.4.2)): + eslint-plugin-vue@9.29.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) - eslint: 9.13.0(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) + eslint: 9.13.0(jiti@2.5.1) globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 semver: 7.6.3 - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.4.2)) + vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@2.5.1)) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -13181,9 +13211,9 @@ snapshots: dependencies: eslint-visitor-keys: 1.3.0 - eslint-utils@3.0.0(eslint@9.13.0(jiti@2.4.2)): + eslint-utils@3.0.0(eslint@9.13.0(jiti@2.5.1)): dependencies: - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-visitor-keys: 2.1.0 eslint-visitor-keys@1.3.0: {} @@ -13237,9 +13267,9 @@ snapshots: transitivePeerDependencies: - supports-color - eslint@9.13.0(jiti@2.4.2): + eslint@9.13.0(jiti@2.5.1): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.11.0 '@eslint/config-array': 0.18.0 '@eslint/core': 0.7.0 @@ -13275,7 +13305,7 @@ snapshots: optionator: 0.9.4 text-table: 0.2.0 optionalDependencies: - jiti: 2.4.2 + jiti: 2.5.1 transitivePeerDependencies: - supports-color @@ -13767,7 +13797,7 @@ snapshots: cookie-es: 1.2.2 crossws: 0.3.4 defu: 6.1.4 - destr: 2.0.3 + destr: 2.0.5 iron-webcrypto: 1.2.1 node-mock-http: 1.0.0 radix3: 1.1.2 @@ -14317,7 +14347,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.4.1 + '@types/node': 20.14.11 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true @@ -15354,14 +15384,14 @@ snapshots: nuxi@3.16.0: {} - nuxt@3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4))(xml2js@0.6.2): + nuxt@3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.43.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue-tsc@2.1.6(typescript@5.5.4))(xml2js@0.6.2): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4)) + '@nuxt/devtools': 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) '@nuxt/telemetry': 2.6.0(magicast@0.3.5)(rollup@4.28.1) - '@nuxt/vite-builder': 3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4)) + '@nuxt/vite-builder': 3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.43.1)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4)) '@unhead/dom': 1.11.13 '@unhead/shared': 1.11.13 '@unhead/ssr': 1.11.13 @@ -15754,6 +15784,16 @@ snapshots: pify@4.0.1: {} + pinia-plugin-persistedstate@4.4.1(@nuxt/kit@3.17.5(magicast@0.3.5))(@pinia/nuxt@0.5.1(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)))(pinia@2.1.7(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))): + dependencies: + deep-pick-omit: 1.2.1 + defu: 6.1.4 + destr: 2.0.5 + optionalDependencies: + '@nuxt/kit': 3.17.5(magicast@0.3.5) + '@pinia/nuxt': 0.5.1(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)) + pinia: 2.1.7(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)) + pinia@2.1.7(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4)): dependencies: '@vue/devtools-api': 6.6.4 @@ -16773,12 +16813,12 @@ snapshots: standard-as-callback@2.1.0: {} - starlight-openapi@0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)))(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0))(openapi-types@12.1.3): + starlight-openapi@0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)))(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0))(openapi-types@12.1.3): dependencies: '@astrojs/markdown-remark': 6.2.0 - '@astrojs/starlight': 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0)) + '@astrojs/starlight': 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0)) '@readme/openapi-parser': 2.5.0(openapi-types@12.1.3) - astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.4.2)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.8.0) + astro: 5.4.1(@types/node@22.4.1)(db0@0.3.2)(jiti@2.5.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.43.1)(typescript@5.8.2)(yaml@2.8.0) github-slugger: 2.0.0 url-template: 3.1.1 transitivePeerDependencies: @@ -17549,17 +17589,17 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-hot-client@0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)): + vite-hot-client@0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)): dependencies: - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) - vite-node@2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0): + vite-node@2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@9.4.0) es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - less @@ -17571,7 +17611,7 @@ snapshots: - supports-color - terser - vite-plugin-checker@0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4)): + vite-plugin-checker@0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue-tsc@2.1.6(typescript@5.5.4)): dependencies: '@babel/code-frame': 7.26.2 ansi-escapes: 4.3.2 @@ -17583,7 +17623,7 @@ snapshots: npm-run-path: 4.0.1 strip-ansi: 6.0.1 tiny-invariant: 1.3.3 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) vscode-languageclient: 7.0.0 vscode-languageserver: 7.0.0 vscode-languageserver-textdocument: 1.0.12 @@ -17594,7 +17634,7 @@ snapshots: typescript: 5.5.4 vue-tsc: 2.1.6(typescript@5.5.4) - vite-plugin-inspect@0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)): + vite-plugin-inspect@0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.3(rollup@4.28.1) @@ -17605,14 +17645,14 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 3.0.0 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) optionalDependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1) transitivePeerDependencies: - rollup - supports-color - vite-plugin-vue-inspector@5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)): + vite-plugin-vue-inspector@5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1)): dependencies: '@babel/core': 7.26.0 '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.26.0) @@ -17623,7 +17663,7 @@ snapshots: '@vue/compiler-dom': 3.5.13 kolorist: 1.8.0 magic-string: 0.30.14 - vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0) + vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1) transitivePeerDependencies: - supports-color @@ -17644,7 +17684,7 @@ snapshots: terser: 5.43.1 optional: true - vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0): + vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.4.49 @@ -17653,9 +17693,9 @@ snapshots: '@types/node': 20.14.11 fsevents: 2.3.3 sass: 1.77.6 - terser: 5.42.0 + terser: 5.43.1 - vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0): + vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.4.49 @@ -17664,9 +17704,9 @@ snapshots: '@types/node': 22.4.1 fsevents: 2.3.3 sass: 1.77.6 - terser: 5.42.0 + terser: 5.43.1 - vite@6.2.0(@types/node@22.4.1)(jiti@2.4.2)(sass@1.77.6)(terser@5.42.0)(yaml@2.8.0): + vite@6.2.0(@types/node@22.4.1)(jiti@2.5.1)(sass@1.77.6)(terser@5.43.1)(yaml@2.8.0): dependencies: esbuild: 0.25.0 postcss: 8.5.3 @@ -17674,14 +17714,14 @@ snapshots: optionalDependencies: '@types/node': 22.4.1 fsevents: 2.3.3 - jiti: 2.4.2 + jiti: 2.5.1 sass: 1.77.6 - terser: 5.42.0 + terser: 5.43.1 yaml: 2.8.0 - vitefu@1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.2)(sass@1.77.6)(terser@5.42.0)(yaml@2.8.0)): + vitefu@1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.5.1)(sass@1.77.6)(terser@5.43.1)(yaml@2.8.0)): optionalDependencies: - vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.2)(sass@1.77.6)(terser@5.42.0)(yaml@2.8.0) + vite: 6.2.0(@types/node@22.4.1)(jiti@2.5.1)(sass@1.77.6)(terser@5.43.1)(yaml@2.8.0) volar-service-css@0.0.62(@volar/language-service@2.4.11): dependencies: @@ -17805,16 +17845,20 @@ snapshots: dependencies: ufo: 1.5.4 + vue-confetti-explosion@1.0.2(vue@3.5.13(typescript@5.5.4)): + dependencies: + vue: 3.5.13(typescript@5.5.4) + vue-demi@0.14.10(vue@3.5.13(typescript@5.5.4)): dependencies: vue: 3.5.13(typescript@5.5.4) vue-devtools-stub@0.1.0: {} - vue-eslint-parser@9.4.3(eslint@9.13.0(jiti@2.4.2)): + vue-eslint-parser@9.4.3(eslint@9.13.0(jiti@2.5.1)): dependencies: debug: 4.4.0(supports-color@9.4.0) - eslint: 9.13.0(jiti@2.4.2) + eslint: 9.13.0(jiti@2.5.1) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 From 1c89b84314ae5f24292da5d8afea33ceeabdca7c Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 31 Jul 2025 12:50:33 -0500 Subject: [PATCH 14/59] fix(moderation): Replace dead modpack link with a valid one in side-types message (#4095) --- .../moderation/data/messages/side-types/inaccurate-modpack.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/moderation/data/messages/side-types/inaccurate-modpack.md b/packages/moderation/data/messages/side-types/inaccurate-modpack.md index dccfba6a..2c66bdec 100644 --- a/packages/moderation/data/messages/side-types/inaccurate-modpack.md +++ b/packages/moderation/data/messages/side-types/inaccurate-modpack.md @@ -4,7 +4,7 @@ Per section 5.1 of %RULES%, it is important that the metadata of your projects i For a brief rundown of how this works: -- Some modpacks can be client-side, usually aimed at providing utility and optimization while allowing the player to join an unmodded server, for instance, [Fabulously Optimized](https://modrinth.com/modpack/fabulously-optimized). -- Most other modpacks that change how the game is played are going to be required on both the client and server, like the modpack [Dying Light](https://modrinth.com/modpack/dying-light). +- Some modpacks can be client-side, usually aimed at providing utility and optimization while allowing the player to join an unmodded server, for instance, [Fabulously Optimized](https://modrinth.com/project/1KVo5zza). +- Most other modpacks that change how the game is played are going to be required on both the client and server, like the modpack [Aged](https://modrinth.com/project/i4XHCd7Q). When in doubt, test for yourself or check the requirements of the mods in your pack. From 3a20e15340f741090667823b028eb382f3f4af7e Mon Sep 17 00:00:00 2001 From: coolbot <76798835+coolbot100s@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:21:28 -0700 Subject: [PATCH 15/59] Coolbot/moderation updates aug1 (#4103) * oop, all commas! * Only show slug stuff when needed. * Move status alerts to top of message, getting rid of separators. * redist libs message altered, and now shows on plugins too * Update versions.ts remove unnecessary import Signed-off-by: coolbot <76798835+coolbot100s@users.noreply.github.com> * Tweak summary formatting msg * Update license messages to use flink * reorder link text to match the settings page * add Description clarity button --------- Signed-off-by: coolbot <76798835+coolbot100s@users.noreply.github.com> --- .../messages/checklist-text/links/base.md | 4 ++-- .../checklist-text/title-slug/slug.md | 3 +++ .../checklist-text/title-slug/title.md | 1 + .../data/messages/description/clarity.md | 7 +++++++ .../messages/description/headers-as-body.md | 2 +- .../data/messages/description/image-only.md | 2 +- .../description/insufficient-packs.md | 2 +- .../description/insufficient-projects.md | 2 +- .../data/messages/description/insufficient.md | 2 +- .../data/messages/description/non-english.md | 2 +- .../messages/description/non-standard-text.md | 2 +- .../data/messages/gallery/not-relevant.md | 2 +- .../data/messages/license/invalid_link.md | 4 ++-- .../data/messages/license/no_source-fork.md | 2 +- .../data/messages/license/no_source.md | 2 +- .../moderation/data/messages/links/misused.md | 2 +- .../data/messages/links/not_accessible.md | 2 +- .../data/messages/reupload/reupload.md | 2 +- .../messages/status-alerts/account_issues.md | 2 -- .../status-alerts/automod_confusion.md | 2 +- .../data/messages/status-alerts/fixed.md | 5 ++--- .../data/messages/status-alerts/private.md | 4 +--- .../data/messages/summary/formatting.md | 4 ++-- .../data/messages/summary/insufficient.md | 2 +- .../data/messages/summary/non-english.md | 2 +- .../data/messages/summary/repeat-title.md | 2 +- .../versions/alternate_versions-additional.md | 2 +- .../alternate_versions-server-additional.md | 2 +- .../versions/alternate_versions-zip.md | 2 +- .../versions/incorrect_additional_files.md | 2 +- .../data/messages/versions/redist_libs.md | 2 +- .../moderation/data/stages/description.ts | 9 ++++++++ .../moderation/data/stages/status-alerts.ts | 8 +++---- packages/moderation/data/stages/title-slug.ts | 21 ++++++++++++++++++- packages/moderation/data/stages/versions.ts | 4 ++-- packages/moderation/utils.ts | 7 ++++--- 36 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 packages/moderation/data/messages/checklist-text/title-slug/slug.md create mode 100644 packages/moderation/data/messages/checklist-text/title-slug/title.md create mode 100644 packages/moderation/data/messages/description/clarity.md diff --git a/packages/moderation/data/messages/checklist-text/links/base.md b/packages/moderation/data/messages/checklist-text/links/base.md index 9b12a8e9..caa71614 100644 --- a/packages/moderation/data/messages/checklist-text/links/base.md +++ b/packages/moderation/data/messages/checklist-text/links/base.md @@ -1,4 +1,4 @@ -**Discord:** %PROJECT_DISCORD_URL% \ **Issues:** %PROJECT_ISSUES_URL% \ **Source:** %PROJECT_SOURCE_URL% \ -**Wiki:** %PROJECT_WIKI_URL% +**Wiki:** %PROJECT_WIKI_URL% \ +**Discord:** %PROJECT_DISCORD_URL% diff --git a/packages/moderation/data/messages/checklist-text/title-slug/slug.md b/packages/moderation/data/messages/checklist-text/title-slug/slug.md new file mode 100644 index 00000000..52e2fa28 --- /dev/null +++ b/packages/moderation/data/messages/checklist-text/title-slug/slug.md @@ -0,0 +1,3 @@ +**Slug:** `%PROJECT_SLUG%`
+ +**Title issues?** diff --git a/packages/moderation/data/messages/checklist-text/title-slug/title.md b/packages/moderation/data/messages/checklist-text/title-slug/title.md new file mode 100644 index 00000000..6901e7eb --- /dev/null +++ b/packages/moderation/data/messages/checklist-text/title-slug/title.md @@ -0,0 +1 @@ +**Title:** %PROJECT_TITLE%
diff --git a/packages/moderation/data/messages/description/clarity.md b/packages/moderation/data/messages/description/clarity.md new file mode 100644 index 00000000..8c59da9d --- /dev/null +++ b/packages/moderation/data/messages/description/clarity.md @@ -0,0 +1,7 @@ +## Description Clarity + +Per section 2 of %RULES% It's important that your Description accurately and honestly represents the content of your project. +Currently, some elements in your Description may be confusing or misleading. +Please edit your description to ensure it accurately represents the current functionality of your project. +Avoid making hyperbolic claims that could misrepresent the facts of your project. +Ensure that your Description is accurate and not likely to confuse users. diff --git a/packages/moderation/data/messages/description/headers-as-body.md b/packages/moderation/data/messages/description/headers-as-body.md index ded6925d..78e8366f 100644 --- a/packages/moderation/data/messages/description/headers-as-body.md +++ b/packages/moderation/data/messages/description/headers-as-body.md @@ -1,6 +1,6 @@ ## Description Accessibility -In accordance with section 2.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) we request that `# header`s not be used as body text. +In accordance with section 2.2 of %RULES%, we request that `# header`s not be used as body text. Headers are interpreted differently by screen-readers and thus should generally only be used for things like separating sections of your Description. diff --git a/packages/moderation/data/messages/description/image-only.md b/packages/moderation/data/messages/description/image-only.md index d827f990..57781986 100644 --- a/packages/moderation/data/messages/description/image-only.md +++ b/packages/moderation/data/messages/description/image-only.md @@ -1,6 +1,6 @@ ## Image Descriptions -In accordance with section 2.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) we ask that you provide a text alternative to your current Description. +In accordance with section 2.2 of %RULES%, we ask that you provide a text alternative to your current Description. It is important that your Description contains enough detail about your project that a user can have a full understanding of it from text alone. diff --git a/packages/moderation/data/messages/description/insufficient-packs.md b/packages/moderation/data/messages/description/insufficient-packs.md index fdd26478..a6467057 100644 --- a/packages/moderation/data/messages/description/insufficient-packs.md +++ b/packages/moderation/data/messages/description/insufficient-packs.md @@ -1,6 +1,6 @@ ## Insufficient Description -Per section 2.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#general-expectations) your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. +Per section 2.1 of %RULES%, your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. Currently, it looks like there are some missing details. diff --git a/packages/moderation/data/messages/description/insufficient-projects.md b/packages/moderation/data/messages/description/insufficient-projects.md index 18f8c4e6..9157b830 100644 --- a/packages/moderation/data/messages/description/insufficient-projects.md +++ b/packages/moderation/data/messages/description/insufficient-projects.md @@ -1,6 +1,6 @@ ## Insufficient Description -Per section 2.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#general-expectations) your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. +Per section 2.1 of %RULES%, your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. Currently, it looks like there are some missing details. diff --git a/packages/moderation/data/messages/description/insufficient.md b/packages/moderation/data/messages/description/insufficient.md index f86ded14..cd8ebc8f 100644 --- a/packages/moderation/data/messages/description/insufficient.md +++ b/packages/moderation/data/messages/description/insufficient.md @@ -1,6 +1,6 @@ ## Insufficient Description -Per section 2.1 of %RULES% your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. +Per section 2.1 of %RULES%, your project's Description should clearly inform the reader of the content, purpose, and appeal of your project. Currently, it looks like there are some missing details. %EXPLAINER% diff --git a/packages/moderation/data/messages/description/non-english.md b/packages/moderation/data/messages/description/non-english.md index 06ba8514..07d83a7d 100644 --- a/packages/moderation/data/messages/description/non-english.md +++ b/packages/moderation/data/messages/description/non-english.md @@ -1,5 +1,5 @@ ## No English Description -Per section 2.2 of %RULES% a project's [Summary](%PROJECT_SETTINGS_LINK%) and %PROJECT_DESCRIPTION_FLINK% must be in English, unless meant exclusively for non-English use, such as translations. +Per section 2.2 of %RULES%, a project's [Summary](%PROJECT_SETTINGS_LINK%) and %PROJECT_DESCRIPTION_FLINK% must be in English, unless meant exclusively for non-English use, such as translations. You may include your non-English Description if you would like but we ask that you also add an English translation of the Description to your project page, if you would like to use an online translator to do this, we recommend [DeepL](https://www.deepl.com/translator). diff --git a/packages/moderation/data/messages/description/non-standard-text.md b/packages/moderation/data/messages/description/non-standard-text.md index be648f27..e7aad989 100644 --- a/packages/moderation/data/messages/description/non-standard-text.md +++ b/packages/moderation/data/messages/description/non-standard-text.md @@ -1,6 +1,6 @@ ## Description Accessibility -Per section 2 of %RULES% your description must be plainly readable and accessible. +Per section 2 of %RULES%, your description must be plainly readable and accessible. Using non-standard text characters like Zalgo or "fancy text" in place of text anywhere in your project, including the Description, Summary, or Title can make your project pages inaccessible. diff --git a/packages/moderation/data/messages/gallery/not-relevant.md b/packages/moderation/data/messages/gallery/not-relevant.md index 78f87966..49986ebb 100644 --- a/packages/moderation/data/messages/gallery/not-relevant.md +++ b/packages/moderation/data/messages/gallery/not-relevant.md @@ -1,3 +1,3 @@ ## Unrelated Gallery Images -Per section 5.5 of %RULES% any images in your project's Gallery must be relevant to the project and also include a Title. +Per section 5.5 of %RULES%, any images in your project's Gallery must be relevant to the project and also include a Title. diff --git a/packages/moderation/data/messages/license/invalid_link.md b/packages/moderation/data/messages/license/invalid_link.md index dec1b977..080d4dea 100644 --- a/packages/moderation/data/messages/license/invalid_link.md +++ b/packages/moderation/data/messages/license/invalid_link.md @@ -1,4 +1,4 @@ ## Invalid License Link -It's important that your project's License link is accurate and leads directly to a valid license for this content. -Your current link: `%PROJECT_LICENSE_URL%` does not appear to lead to a valid license for this project, or it is not publicly accessable. +It's important that your project's %PROJECT_LICENSE_FLINK% link is accurate and leads directly to a valid license for this content. +Your current link: `%PROJECT_LICENSE_URL%` does not appear to lead to a valid license for this project, or it is not publicly accessible. diff --git a/packages/moderation/data/messages/license/no_source-fork.md b/packages/moderation/data/messages/license/no_source-fork.md index 1ff659c7..ded248ea 100644 --- a/packages/moderation/data/messages/license/no_source-fork.md +++ b/packages/moderation/data/messages/license/no_source-fork.md @@ -1,5 +1,5 @@ ## No Source Code Provided -Your project's license of `%PROJECT_LICENSE_NAME%`, requires source disclosure. +Your project's %PROJECT_LICENSE_FLINK% of `%PROJECT_LICENSE_NAME%`, requires source disclosure. Consider adding a Source link to your project's repository, or including a Sources file for each version as an Additional File. Keep in mind this may be a requirement of the source work's licensing, which must be abided per section 4 of %RULES%. diff --git a/packages/moderation/data/messages/license/no_source.md b/packages/moderation/data/messages/license/no_source.md index 76e7c7a9..106efae0 100644 --- a/packages/moderation/data/messages/license/no_source.md +++ b/packages/moderation/data/messages/license/no_source.md @@ -1,4 +1,4 @@ ## No Source Code Provided -Your project's license of `%PROJECT_LICENSE_NAME%`, requires source disclosure. +Your project's %PROJECT_LICENSE_FLINK% of `%PROJECT_LICENSE_NAME%`, requires source disclosure. Consider adding a Source link to your project's repository, or including a Sources file for each version as an Additional File. You may also want to refer to %LICENSING_GUIDE% if you wish to select a different License, remember to make sure your selected License is consistent with the license in your project's files as well. diff --git a/packages/moderation/data/messages/links/misused.md b/packages/moderation/data/messages/links/misused.md index 15582bee..22fe93ed 100644 --- a/packages/moderation/data/messages/links/misused.md +++ b/packages/moderation/data/messages/links/misused.md @@ -1,4 +1,4 @@ ## Misuse of Links -Per section 5.4 of %RULES% all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project. +Per section 5.4 of %RULES%, all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project. Currently it looks like your %MISUSED_LINKS% link(s) are misused or incorrectly labeled. diff --git a/packages/moderation/data/messages/links/not_accessible.md b/packages/moderation/data/messages/links/not_accessible.md index 50f868ba..47b60f5c 100644 --- a/packages/moderation/data/messages/links/not_accessible.md +++ b/packages/moderation/data/messages/links/not_accessible.md @@ -1,3 +1,3 @@ ## Unreachable Links -Per section 5.4 of %RULES% all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project. +Per section 5.4 of %RULES%, all %PROJECT_LINKS_FLINK% must lead to correctly labeled publicly available resources that are directly related to your project. diff --git a/packages/moderation/data/messages/reupload/reupload.md b/packages/moderation/data/messages/reupload/reupload.md index 51fcab85..6c2cc9b7 100644 --- a/packages/moderation/data/messages/reupload/reupload.md +++ b/packages/moderation/data/messages/reupload/reupload.md @@ -1,5 +1,5 @@ ## Reuploads are forbidden This project appears to contain content from %ORIGINAL_PROJECT% by %ORIGINAL_AUTHOR%. -Per section 4 of %RULES% this is strictly forbidden. +Per section 4 of %RULES%, this is strictly forbidden. If you believe this is an error, or you can verify you are the creator and rightful owner of this content please let us know. Otherwise, we ask that you **do not resubmit this project**. diff --git a/packages/moderation/data/messages/status-alerts/account_issues.md b/packages/moderation/data/messages/status-alerts/account_issues.md index 9c4fdc27..067e3669 100644 --- a/packages/moderation/data/messages/status-alerts/account_issues.md +++ b/packages/moderation/data/messages/status-alerts/account_issues.md @@ -1,5 +1,3 @@ ---- - ## Account Issues Indicated We're sorry to hear you're having trouble accessing your accounts, unfortunately, our moderation team is unable to assist with account-related issues. diff --git a/packages/moderation/data/messages/status-alerts/automod_confusion.md b/packages/moderation/data/messages/status-alerts/automod_confusion.md index 842f5b54..9acdaee0 100644 --- a/packages/moderation/data/messages/status-alerts/automod_confusion.md +++ b/packages/moderation/data/messages/status-alerts/automod_confusion.md @@ -1,4 +1,4 @@ ---- +## Warnings from AutoMod Unfortunately, our AutoMod cannot read your project's Description or your messages to moderation. AutoMod will warn both you and our Moderation Staff about potential issues, but if you've already followed the necessary steps these warnings can safely be ignored. diff --git a/packages/moderation/data/messages/status-alerts/fixed.md b/packages/moderation/data/messages/status-alerts/fixed.md index 43358862..1300fd7b 100644 --- a/packages/moderation/data/messages/status-alerts/fixed.md +++ b/packages/moderation/data/messages/status-alerts/fixed.md @@ -1,5 +1,4 @@ ---- - ## Corrections Applied -I've gone ahead and corrected the issues listed above so your project can be Approved. +Your submission contained some issues which may have prevented your project from being published. +These have been corrected by our Moderation Team so your project can be Approved, be sure to read and understand each issue listed below to ensure a smooth review for your next submission. diff --git a/packages/moderation/data/messages/status-alerts/private.md b/packages/moderation/data/messages/status-alerts/private.md index cb9cd608..ed82add5 100644 --- a/packages/moderation/data/messages/status-alerts/private.md +++ b/packages/moderation/data/messages/status-alerts/private.md @@ -1,8 +1,6 @@ ---- - ## Private Use -Under normal circumstances, your project would be rejected due to the issues listed above. +Under normal circumstances, your project would be rejected due to the issues listed below. However, since your project is not intended for public use, these requirements will be waived and your project will be unlisted. This means it will remain accessible through a direct link without appearing in public search results, allowing you to share it privately. If you're okay with this, or submitted your project to be unlisted already, than no further action is necessary. If you would like to publish your project publicly, please address all moderation concerns before resubmitting this project. diff --git a/packages/moderation/data/messages/summary/formatting.md b/packages/moderation/data/messages/summary/formatting.md index 8b9704a1..f641e593 100644 --- a/packages/moderation/data/messages/summary/formatting.md +++ b/packages/moderation/data/messages/summary/formatting.md @@ -1,6 +1,6 @@ -## Insufficient Summary +## Invalid Summary Formatting -Per section 5.3 of %RULES% your Summary can not include any extra formatting such as lists, or links. +Per section 5.3 of %RULES%, your Summary can not include any extra formatting such as lists, or links. Your project summary should provide a brief overview of your project that informs and entices users. diff --git a/packages/moderation/data/messages/summary/insufficient.md b/packages/moderation/data/messages/summary/insufficient.md index 2505b8ba..12e012ca 100644 --- a/packages/moderation/data/messages/summary/insufficient.md +++ b/packages/moderation/data/messages/summary/insufficient.md @@ -1,5 +1,5 @@ ## Insufficient Summary -Per section 5.3 of %RULES% your project summary should provide a brief overview of your project that informs and entices users. +Per section 5.3 of %RULES%, your project summary should provide a brief overview of your project that informs and entices users. This is the first thing most people will see about your mod other than the Logo, so it's important it be accurate, reasonably detailed, and exciting. diff --git a/packages/moderation/data/messages/summary/non-english.md b/packages/moderation/data/messages/summary/non-english.md index 64059f4d..94c68096 100644 --- a/packages/moderation/data/messages/summary/non-english.md +++ b/packages/moderation/data/messages/summary/non-english.md @@ -1,5 +1,5 @@ ## No English Summary -Per section 2.2 of %RULES% a project's Summary and Description must be in English, unless meant exclusively for non-English use, such as translations. +Per section 2.2 of %RULES%, a project's Summary and Description must be in English, unless meant exclusively for non-English use, such as translations. You may include your non-English Summary but we ask that you also add an English translation. diff --git a/packages/moderation/data/messages/summary/repeat-title.md b/packages/moderation/data/messages/summary/repeat-title.md index e6706138..e16fc805 100644 --- a/packages/moderation/data/messages/summary/repeat-title.md +++ b/packages/moderation/data/messages/summary/repeat-title.md @@ -1,6 +1,6 @@ ## Insufficient Summary -Per section 5.3 of %RULES% your Summary can not be the same as your project's Title. +Per section 5.3 of %RULES%, your Summary can not be the same as your project's Title. Your project summary should provide a brief overview of your project that informs and entices users. diff --git a/packages/moderation/data/messages/versions/alternate_versions-additional.md b/packages/moderation/data/messages/versions/alternate_versions-additional.md index ad9023cd..fdb9e910 100644 --- a/packages/moderation/data/messages/versions/alternate_versions-additional.md +++ b/packages/moderation/data/messages/versions/alternate_versions-additional.md @@ -1,5 +1,5 @@ ## Unsupported Project -Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules), Modrinth does not support uploading multiple variations of your project as Additional files. +Per section 5.7 of %RULES%, Modrinth does not support uploading multiple variations of your project as Additional files. Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want. We ask that you upload each alternate version of your project as a new project, ensuring that all users will be able to access and easily find your content. diff --git a/packages/moderation/data/messages/versions/alternate_versions-server-additional.md b/packages/moderation/data/messages/versions/alternate_versions-server-additional.md index 7095cd84..51a9e287 100644 --- a/packages/moderation/data/messages/versions/alternate_versions-server-additional.md +++ b/packages/moderation/data/messages/versions/alternate_versions-server-additional.md @@ -1,4 +1,4 @@ ## Incorrect Additional Files -Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) the additional files section should only be used for specific designated purposes such as a `Sources.jar`. +Per section 5.7 of %RULES%, the additional files section should only be used for specific designated purposes such as a `Sources.jar`. To ensure a smooth experience for you and your users, please upload each alternate version of your modpack as its own Modpack project, thank you. diff --git a/packages/moderation/data/messages/versions/alternate_versions-zip.md b/packages/moderation/data/messages/versions/alternate_versions-zip.md index 284ffa87..70fafd93 100644 --- a/packages/moderation/data/messages/versions/alternate_versions-zip.md +++ b/packages/moderation/data/messages/versions/alternate_versions-zip.md @@ -1,5 +1,5 @@ ## Incorrect Additional Files -Per section 5.7 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) the additional files section should only be used for specific designated purposes such as a `Sources.jar`. +Per section 5.7 of %RULES%, the additional files section should only be used for specific designated purposes such as a `Sources.jar`. Modrinth does not support the upload of modpacks in the `.zip` format, as this may cause issues for Modrinth users or distribute copyrighted content without the proper permissions. If you would like to upload a server-specific version of your modpack, consider creating a separate Modpack project. diff --git a/packages/moderation/data/messages/versions/incorrect_additional_files.md b/packages/moderation/data/messages/versions/incorrect_additional_files.md index 475367e1..6756d8d2 100644 --- a/packages/moderation/data/messages/versions/incorrect_additional_files.md +++ b/packages/moderation/data/messages/versions/incorrect_additional_files.md @@ -1,5 +1,5 @@ ## Incorrect Use of Additional Files -It looks like you've uploaded multiple primary files to one Version as Additional Files. Per section 5.7 of %RULES% each Version of your project must include only one primary file that corresponds to its respective Minecraft and loader versions. +It looks like you've uploaded multiple primary files to one Version as Additional Files. Per section 5.7 of %RULES%, each Version of your project must include only one primary file that corresponds to its respective Minecraft and loader versions. This allows users to easily find and download the content they need for their game profile with ease. The Additional Files feature can be used for things like a `Sources.jar`. Please upload each version of your project separately, thank you. diff --git a/packages/moderation/data/messages/versions/redist_libs.md b/packages/moderation/data/messages/versions/redist_libs.md index be618980..ffb96463 100644 --- a/packages/moderation/data/messages/versions/redist_libs.md +++ b/packages/moderation/data/messages/versions/redist_libs.md @@ -1,4 +1,4 @@ -## Excessive File Size +## Unnecessary redistribution of dependencies This project appears to include libs or dependencies, unnecessarily redistributing their entire contents. This is often due to an error in project structure or compilation, and in some cases, may violate the copyrights or licensing agreements of these libraries. diff --git a/packages/moderation/data/stages/description.ts b/packages/moderation/data/stages/description.ts index beab7514..f68d142e 100644 --- a/packages/moderation/data/stages/description.ts +++ b/packages/moderation/data/stages/description.ts @@ -94,6 +94,15 @@ const description: Stage = { message: async () => (await import('../messages/description/non-standard-text.md?raw')).default, } as ButtonAction, + { + id: 'description_clarity', + type: 'button', + label: 'Unclear / Misleading', + weight: 407, + suggestedStatus: 'rejected', + severity: 'high', + message: async () => (await import('../messages/description/clarity.md?raw')).default, + } as ButtonAction, ], } diff --git a/packages/moderation/data/stages/status-alerts.ts b/packages/moderation/data/stages/status-alerts.ts index 6c8dbc53..7e0d7724 100644 --- a/packages/moderation/data/stages/status-alerts.ts +++ b/packages/moderation/data/stages/status-alerts.ts @@ -15,7 +15,7 @@ const statusAlerts: Stage = { id: 'status_corrections_applied', type: 'button', label: 'Corrections applied', - weight: 999999, + weight: -999999, suggestedStatus: 'approved', disablesActions: ['status_private_use', 'status_account_issues'], message: async () => (await import('../messages/status-alerts/fixed.md?raw')).default, @@ -24,7 +24,7 @@ const statusAlerts: Stage = { id: 'status_private_use', type: 'button', label: 'Private use', - weight: 999999, + weight: -999999, suggestedStatus: 'flagged', disablesActions: ['status_corrections_applied', 'status_account_issues'], message: async () => (await import('../messages/status-alerts/private.md?raw')).default, @@ -33,7 +33,7 @@ const statusAlerts: Stage = { id: 'status_account_issues', type: 'button', label: 'Account issues', - weight: 999999, + weight: -999999, suggestedStatus: 'rejected', disablesActions: ['status_corrections_applied', 'status_private_use'], message: async () => @@ -78,7 +78,7 @@ const statusAlerts: Stage = { id: 'status_automod_confusion', type: 'button', label: `Automod confusion`, - weight: 999999, + weight: -999999, message: async () => (await import('../messages/status-alerts/automod_confusion.md?raw')).default, } as ButtonAction, diff --git a/packages/moderation/data/stages/title-slug.ts b/packages/moderation/data/stages/title-slug.ts index 4fe699a6..55d32a62 100644 --- a/packages/moderation/data/stages/title-slug.ts +++ b/packages/moderation/data/stages/title-slug.ts @@ -1,10 +1,28 @@ import { BookOpenIcon } from '@modrinth/assets' import type { Stage } from '../../types/stage' +import type { Project } from '@modrinth/utils' + +function hasCustomSlug(project: Project): boolean { + return ( + project.slug !== + project.title + .trim() + .toLowerCase() + .replaceAll(' ', '-') + .replaceAll(/[^a-zA-Z0-9!@$()`.+,_"-]/g, '') + .replaceAll(/--+/gm, '-') + ) +} const titleSlug: Stage = { title: 'Are the Name and URL accurate and appropriate?', id: 'title-&-slug', - text: async () => (await import('../messages/checklist-text/title-slug.md?raw')).default, + text: async (project) => { + let text = (await import('../messages/checklist-text/title-slug/title.md?raw')).default + if (hasCustomSlug(project)) + text += (await import('../messages/checklist-text/title-slug/slug.md?raw')).default + return text + }, icon: BookOpenIcon, guidance_url: 'https://modrinth.com/legal/rules#miscellaneous', actions: [ @@ -63,6 +81,7 @@ const titleSlug: Stage = { label: 'Slug issues?', suggestedStatus: 'rejected', severity: 'low', + shouldShow: (project) => hasCustomSlug(project), options: [ { label: 'Misused', diff --git a/packages/moderation/data/stages/versions.ts b/packages/moderation/data/stages/versions.ts index 7f5ca33a..bf8e3196 100644 --- a/packages/moderation/data/stages/versions.ts +++ b/packages/moderation/data/stages/versions.ts @@ -135,11 +135,11 @@ const versions: Stage = { { id: 'versions_redist_libs', type: 'button', - label: 'Oversized File', + label: 'Packed Libs', suggestedStatus: `rejected`, severity: `medium`, weight: 1003, - shouldShow: (project) => project.project_type === 'mod', + shouldShow: (project) => project.project_type === 'mod' || project.project_type === 'plugin', message: async () => (await import('../messages/versions/redist_libs.md?raw')).default, } as ButtonAction, { diff --git a/packages/moderation/utils.ts b/packages/moderation/utils.ts index 9aafa058..9feb8c27 100644 --- a/packages/moderation/utils.ts +++ b/packages/moderation/utils.ts @@ -259,7 +259,7 @@ export function flattenProjectVariables(project: Project): Record Date: Fri, 1 Aug 2025 21:24:40 +0100 Subject: [PATCH 16/59] fix: approve status incorrect (#4104) --- .../ui/moderation/checklist/ModerationChecklist.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue index d456c5b8..0bb5c736 100644 --- a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue @@ -259,7 +259,7 @@ - @@ -355,6 +355,7 @@ import { renderHighlightedString, type ModerationJudgements, type ModerationModpackItem, + type ProjectStatus, } from "@modrinth/utils"; import { computedAsync, useLocalStorage } from "@vueuse/core"; import { @@ -527,7 +528,7 @@ function handleKeybinds(event: KeyboardEvent) { tryResetProgress: resetProgress, tryExitModeration: () => emit("exit"), - tryApprove: () => sendMessage("approved"), + tryApprove: () => sendMessage(props.project.requested_status), tryReject: () => sendMessage("rejected"), tryWithhold: () => sendMessage("withheld"), tryEditMessage: goBackToStages, @@ -1208,7 +1209,7 @@ function generateModpackMessage(allFiles: { } const hasNextProject = ref(false); -async function sendMessage(status: "approved" | "rejected" | "withheld") { +async function sendMessage(status: ProjectStatus) { try { await useBaseFetch(`project/${props.project.id}`, { method: "PATCH", From b33e12c71de776b3af3970547202ccb61f63b9cf Mon Sep 17 00:00:00 2001 From: IMB11 Date: Fri, 1 Aug 2025 22:22:22 +0100 Subject: [PATCH 17/59] fix: startup settings not visible on hard page refresh/direct load (#4100) * fix: startup settings not visible on hard page refresh/direct load * refactor: const func => named --- .../servers/manage/[id]/options/startup.vue | 53 +++++++------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue b/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue index bd9a7edd..7080f0e1 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue @@ -42,7 +42,7 @@ - @@ -73,106 +75,117 @@ - + diff --git a/packages/ui/src/providers/index.ts b/packages/ui/src/providers/index.ts new file mode 100644 index 00000000..c8859edc --- /dev/null +++ b/packages/ui/src/providers/index.ts @@ -0,0 +1,81 @@ +/** + * MIT License + * + * Copyright (c) 2023 UnoVue + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @source https://github.com/unovue/reka-ui/blob/53b4734734f8ebef9a344b1e62db291177c59bfe/packages/core/src/shared/createContext.ts + */ + +import type { InjectionKey } from 'vue' +import { inject, provide } from 'vue' + +/** + * @param providerComponentName - The name(s) of the component(s) providing the context. + * + * There are situations where context can come from multiple components. In such cases, you might need to give an array of component names to provide your context, instead of just a single string. + * + * @param contextName The description for injection key symbol. + */ +export function createContext( + providerComponentName: string | string[], + contextName?: string, +) { + const symbolDescription = + typeof providerComponentName === 'string' && !contextName + ? `${providerComponentName}Context` + : contextName + + const injectionKey: InjectionKey = Symbol(symbolDescription) + + /** + * @param fallback The context value to return if the injection fails. + * + * @throws When context injection failed and no fallback is specified. + * This happens when the component injecting the context is not a child of the root component providing the context. + */ + const injectContext = ( + fallback?: T, + ): T extends null ? ContextValue | null : ContextValue => { + const context = inject(injectionKey, fallback) + if (context) return context + + if (context === null) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return context as any + + throw new Error( + `Injection \`${injectionKey.toString()}\` not found. Component must be used within ${ + Array.isArray(providerComponentName) + ? `one of the following components: ${providerComponentName.join(', ')}` + : `\`${providerComponentName}\`` + }`, + ) + } + + const provideContext = (contextValue: ContextValue) => { + provide(injectionKey, contextValue) + return contextValue + } + + return [injectContext, provideContext] as const +} + +export * from './web-notifications' diff --git a/packages/ui/src/providers/web-notifications.ts b/packages/ui/src/providers/web-notifications.ts new file mode 100644 index 00000000..c56507b0 --- /dev/null +++ b/packages/ui/src/providers/web-notifications.ts @@ -0,0 +1,133 @@ +import { createContext } from '.' + +export interface WebNotification { + id: string | number + title?: string + text?: string + type?: 'error' | 'warning' | 'success' | 'info' + errorCode?: string + count?: number + timer?: NodeJS.Timeout +} + +export type NotificationPanelLocation = 'left' | 'right' + +export abstract class AbstractWebNotificationManager { + protected readonly AUTO_DISMISS_DELAY_MS = 30 * 1000 + + abstract getNotifications(): WebNotification[] + abstract getNotificationLocation(): NotificationPanelLocation + abstract setNotificationLocation(location: NotificationPanelLocation): void + + protected abstract addNotificationToStorage(notification: WebNotification): void + protected abstract removeNotificationFromStorage(id: string | number): void + protected abstract removeNotificationFromStorageByIndex(index: number): void + protected abstract clearAllNotificationsFromStorage(): void + + addNotification = (notification: Partial): WebNotification => { + const existingNotif = this.findExistingNotification(notification) + + if (existingNotif) { + this.refreshNotificationTimer(existingNotif) + existingNotif.count = (existingNotif.count || 0) + 1 + return existingNotif + } + + const newNotification = this.createNotification(notification) + this.setNotificationTimer(newNotification) + this.addNotificationToStorage(newNotification) + return newNotification + } + + /** + * @deprecated You should use `addNotification` instead to provide a more human-readable error message to the user. + */ + handleError = (error: Error): void => { + this.addNotification({ + title: 'An error occurred', + text: error.message ?? error, + type: 'error', + }) + } + + removeNotification = (id: string | number): WebNotification | undefined => { + const notifications = this.getNotifications() + const notification = notifications.find((n) => n.id === id) + + if (notification) { + this.clearNotificationTimer(notification) + this.removeNotificationFromStorage(id) + } + + return notification + } + + removeNotificationByIndex = (index: number): WebNotification | null => { + const notifications = this.getNotifications() + + if (index >= 0 && index < notifications.length) { + const notification = notifications[index] + this.clearNotificationTimer(notification) + this.removeNotificationFromStorageByIndex(index) + + return notification + } + + return null + } + + clearAllNotifications = (): void => { + const notifications = this.getNotifications() + notifications.forEach((notification) => { + this.clearNotificationTimer(notification) + }) + this.clearAllNotificationsFromStorage() + } + + setNotificationTimer = (notification: WebNotification): void => { + if (!notification) return + + this.clearNotificationTimer(notification) + + notification.timer = setTimeout(() => { + this.removeNotification(notification.id) + }, this.AUTO_DISMISS_DELAY_MS) + } + + stopNotificationTimer = (notification: WebNotification): void => { + this.clearNotificationTimer(notification) + } + + private refreshNotificationTimer(notification: WebNotification): void { + this.setNotificationTimer(notification) + } + + private clearNotificationTimer(notification: WebNotification): void { + if (notification.timer) { + clearTimeout(notification.timer) + notification.timer = undefined + } + } + + private findExistingNotification( + notification: Partial, + ): WebNotification | undefined { + return this.getNotifications().find( + (existing) => + existing.text === notification.text && + existing.title === notification.title && + existing.type === notification.type, + ) + } + + private createNotification(notification: Partial): WebNotification { + return { + ...notification, + id: new Date().getTime(), + count: 1, + } as WebNotification + } +} + +export const [injectNotificationManager, provideNotificationManager] = + createContext('root', 'notificationManager') diff --git a/packages/utils/types.ts b/packages/utils/types.ts index bc3f33d9..ede87cab 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -49,6 +49,76 @@ export interface GalleryImage { description?: string } +export interface ProjectV3 { + id: ModrinthId + slug?: string + project_types: string[] + games: string[] + team_id: ModrinthId + organization?: ModrinthId + name: string + summary: string + description: string + + published: string + updated: string + approved?: string + queued?: string + + status: ProjectStatus + requested_status?: ProjectStatus + + /** @deprecated moved to threads system */ + moderator_message?: { + message: string + body?: string + } + + license: { + id: string + name: string + url?: string + } + + downloads: number + followers: number + + categories: string[] + additional_categories: string[] + loaders: string[] + + versions: ModrinthId[] + icon_url?: string + + link_urls: Record< + string, + { + platform: string + donation: boolean + url: string + } + > + + gallery: { + url: string + raw_url: string + featured: boolean + name?: string + description?: string + created: string + ordering: number + }[] + + color?: number + thread_id: ModrinthId + monetization_status: MonetizationStatus + side_types_migration_review_status: 'reviewed' | 'pending' + + [key: string]: unknown +} + +export type SideTypesMigrationReviewStatus = 'reviewed' | 'pending' + export interface Project { id: ModrinthId project_type: ProjectType From 5ffcc48d75a94f93449e96b7d7ee65f2ef07eca8 Mon Sep 17 00:00:00 2001 From: Josiah Glosson Date: Wed, 13 Aug 2025 16:28:44 -0700 Subject: [PATCH 42/59] Implement a more robust IPC system between the launcher and client (#4159) * Implement a more robust IPC system between the launcher and client * Clippy fix and cargo fmt * Switch to cached JsonReader with LENIENT parsing to avoid race conditions * Make RPC send messages in lines * Try to bind to either IPv4 or IPv6 and communicate version * Move message handling into a separate function to avoid too much code in a macro --- .../src/api/oauth_utils/auth_code_reply.rs | 20 +- packages/app-lib/build.rs | 1 - packages/app-lib/java/build.gradle.kts | 1 + .../com/modrinth/theseus/MinecraftLaunch.java | 48 +--- .../com/modrinth/theseus/rpc/RpcHandlers.java | 46 ++++ .../theseus/rpc/RpcMethodException.java | 9 + .../com/modrinth/theseus/rpc/TheseusRpc.java | 183 +++++++++++++ packages/app-lib/src/api/mod.rs | 5 +- packages/app-lib/src/error.rs | 3 + packages/app-lib/src/launcher/args.rs | 7 + packages/app-lib/src/launcher/mod.rs | 19 +- packages/app-lib/src/state/process.rs | 19 +- packages/app-lib/src/util/mod.rs | 2 + packages/app-lib/src/util/network.rs | 17 ++ packages/app-lib/src/util/rpc.rs | 258 ++++++++++++++++++ 15 files changed, 568 insertions(+), 70 deletions(-) create mode 100644 packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcHandlers.java create mode 100644 packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcMethodException.java create mode 100644 packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/TheseusRpc.java create mode 100644 packages/app-lib/src/util/network.rs create mode 100644 packages/app-lib/src/util/rpc.rs diff --git a/apps/app/src/api/oauth_utils/auth_code_reply.rs b/apps/app/src/api/oauth_utils/auth_code_reply.rs index 4e4a5292..fedffcb0 100644 --- a/apps/app/src/api/oauth_utils/auth_code_reply.rs +++ b/apps/app/src/api/oauth_utils/auth_code_reply.rs @@ -11,7 +11,7 @@ //! [RFC 8252]: https://datatracker.ietf.org/doc/html/rfc8252 use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + net::SocketAddr, sync::{LazyLock, Mutex}, time::Duration, }; @@ -19,10 +19,8 @@ use std::{ use hyper::body::Incoming; use hyper_util::rt::{TokioIo, TokioTimer}; use theseus::ErrorKind; -use tokio::{ - net::TcpListener, - sync::{broadcast, oneshot}, -}; +use theseus::prelude::tcp_listen_any_loopback; +use tokio::sync::{broadcast, oneshot}; static SERVER_SHUTDOWN: LazyLock> = LazyLock::new(|| broadcast::channel(1024).0); @@ -35,17 +33,7 @@ static SERVER_SHUTDOWN: LazyLock> = pub async fn listen( listen_socket_tx: oneshot::Sender>, ) -> Result, theseus::Error> { - // IPv4 is tried first for the best compatibility and performance with most systems. - // IPv6 is also tried in case IPv4 is not available. Resolving "localhost" is avoided - // to prevent failures deriving from improper name resolution setup. Any available - // ephemeral port is used to prevent conflicts with other services. This is all as per - // RFC 8252's recommendations - const ANY_LOOPBACK_SOCKET: &[SocketAddr] = &[ - SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), - SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0), - ]; - - let listener = match TcpListener::bind(ANY_LOOPBACK_SOCKET).await { + let listener = match tcp_listen_any_loopback().await { Ok(listener) => { listen_socket_tx .send(listener.local_addr().map_err(|e| { diff --git a/packages/app-lib/build.rs b/packages/app-lib/build.rs index 48da4b45..10ed29b9 100644 --- a/packages/app-lib/build.rs +++ b/packages/app-lib/build.rs @@ -53,7 +53,6 @@ fn build_java_jars() { .arg("build") .arg("--no-daemon") .arg("--console=rich") - .arg("--info") .current_dir(dunce::canonicalize("java").unwrap()) .status() .expect("Failed to wait on Gradle build"); diff --git a/packages/app-lib/java/build.gradle.kts b/packages/app-lib/java/build.gradle.kts index a671dd6f..98c95c8c 100644 --- a/packages/app-lib/java/build.gradle.kts +++ b/packages/app-lib/java/build.gradle.kts @@ -11,6 +11,7 @@ repositories { dependencies { implementation("org.ow2.asm:asm:9.8") implementation("org.ow2.asm:asm-tree:9.8") + implementation("com.google.code.gson:gson:2.13.1") testImplementation(libs.junit.jupiter) testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/packages/app-lib/java/src/main/java/com/modrinth/theseus/MinecraftLaunch.java b/packages/app-lib/java/src/main/java/com/modrinth/theseus/MinecraftLaunch.java index 9d61a0c0..b474ba02 100644 --- a/packages/app-lib/java/src/main/java/com/modrinth/theseus/MinecraftLaunch.java +++ b/packages/app-lib/java/src/main/java/com/modrinth/theseus/MinecraftLaunch.java @@ -1,11 +1,13 @@ package com.modrinth.theseus; -import java.io.ByteArrayOutputStream; +import com.modrinth.theseus.rpc.RpcHandlers; +import com.modrinth.theseus.rpc.TheseusRpc; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.concurrent.CompletableFuture; public final class MinecraftLaunch { public static void main(String[] args) throws IOException, ReflectiveOperationException { @@ -13,45 +15,19 @@ public final class MinecraftLaunch { final String[] gameArgs = Arrays.copyOfRange(args, 1, args.length); System.setProperty("modrinth.process.args", String.join("\u001f", gameArgs)); - parseInput(); + final CompletableFuture waitForLaunch = new CompletableFuture<>(); + TheseusRpc.connectAndStart( + System.getProperty("modrinth.internal.ipc.host"), + Integer.getInteger("modrinth.internal.ipc.port"), + new RpcHandlers() + .handler("set_system_property", String.class, String.class, System::setProperty) + .handler("launch", () -> waitForLaunch.complete(null))); + + waitForLaunch.join(); relaunch(mainClass, gameArgs); } - private static void parseInput() throws IOException { - final ByteArrayOutputStream line = new ByteArrayOutputStream(); - while (true) { - final int b = System.in.read(); - if (b < 0) { - throw new IllegalStateException("Stdin terminated while parsing"); - } - if (b != '\n') { - line.write(b); - continue; - } - if (handleLine(line.toString("UTF-8"))) { - break; - } - line.reset(); - } - } - - private static boolean handleLine(String line) { - final String[] parts = line.split("\t", 2); - switch (parts[0]) { - case "property": { - final String[] keyValue = parts[1].split("\t", 2); - System.setProperty(keyValue[0], keyValue[1]); - return false; - } - case "launch": - return true; - } - - System.err.println("Unknown input line " + line); - return false; - } - private static void relaunch(String mainClassName, String[] args) throws ReflectiveOperationException { final int javaVersion = getJavaVersion(); final Class mainClass = Class.forName(mainClassName); diff --git a/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcHandlers.java b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcHandlers.java new file mode 100644 index 00000000..257148ef --- /dev/null +++ b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcHandlers.java @@ -0,0 +1,46 @@ +package com.modrinth.theseus.rpc; + +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class RpcHandlers { + private final Map> handlers = new HashMap<>(); + private boolean frozen; + + public RpcHandlers handler(String functionName, Runnable handler) { + return addHandler(functionName, args -> { + handler.run(); + return JsonNull.INSTANCE; + }); + } + + public RpcHandlers handler( + String functionName, Class arg1Type, Class arg2Type, BiConsumer handler) { + return addHandler(functionName, args -> { + if (args.length != 2) { + throw new IllegalArgumentException(functionName + " expected 2 arguments"); + } + final A arg1 = TheseusRpc.GSON.fromJson(args[0], arg1Type); + final B arg2 = TheseusRpc.GSON.fromJson(args[1], arg2Type); + handler.accept(arg1, arg2); + return JsonNull.INSTANCE; + }); + } + + private RpcHandlers addHandler(String functionName, Function handler) { + if (frozen) { + throw new IllegalStateException("Cannot add handler to frozen RpcHandlers instance"); + } + handlers.put(functionName, handler); + return this; + } + + Map> build() { + frozen = true; + return handlers; + } +} diff --git a/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcMethodException.java b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcMethodException.java new file mode 100644 index 00000000..f9ab75a3 --- /dev/null +++ b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/RpcMethodException.java @@ -0,0 +1,9 @@ +package com.modrinth.theseus.rpc; + +public class RpcMethodException extends RuntimeException { + private static final long serialVersionUID = 1922360184188807964L; + + public RpcMethodException(String message) { + super(message); + } +} diff --git a/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/TheseusRpc.java b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/TheseusRpc.java new file mode 100644 index 00000000..ff460ff8 --- /dev/null +++ b/packages/app-lib/java/src/main/java/com/modrinth/theseus/rpc/TheseusRpc.java @@ -0,0 +1,183 @@ +package com.modrinth.theseus.rpc; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import java.io.*; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +public final class TheseusRpc { + static final Gson GSON = new GsonBuilder() + .setStrictness(Strictness.STRICT) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .disableHtmlEscaping() + .create(); + private static final TypeToken MESSAGE_TYPE = TypeToken.get(RpcMessage.class); + + private static final AtomicReference RPC = new AtomicReference<>(); + + private final BlockingQueue mainThreadQueue = new LinkedBlockingQueue<>(); + private final Map> awaitingResponse = new ConcurrentHashMap<>(); + private final Map> handlers; + private final Socket socket; + + private TheseusRpc(Socket socket, RpcHandlers handlers) { + this.socket = socket; + this.handlers = handlers.build(); + } + + public static void connectAndStart(String host, int port, RpcHandlers handlers) throws IOException { + if (RPC.get() != null) { + throw new IllegalStateException("Can only connect to RPC once"); + } + + final Socket socket = new Socket(host, port); + final TheseusRpc rpc = new TheseusRpc(socket, handlers); + final Thread mainThread = new Thread(rpc::mainThread, "Theseus RPC Main"); + final Thread readThread = new Thread(rpc::readThread, "Theseus RPC Read"); + mainThread.setDaemon(true); + readThread.setDaemon(true); + mainThread.start(); + readThread.start(); + RPC.set(rpc); + } + + public static TheseusRpc getRpc() { + final TheseusRpc rpc = RPC.get(); + if (rpc == null) { + throw new IllegalStateException("Called getRpc before RPC initialized"); + } + return rpc; + } + + public CompletableFuture callMethod(TypeToken returnType, String method, Object... args) { + final JsonElement[] jsonArgs = new JsonElement[args.length]; + for (int i = 0; i < args.length; i++) { + jsonArgs[i] = GSON.toJsonTree(args[i]); + } + + final RpcMessage message = new RpcMessage(method, jsonArgs); + final ResponseWaiter responseWaiter = new ResponseWaiter<>(returnType); + awaitingResponse.put(message.id, responseWaiter); + mainThreadQueue.add(message); + return responseWaiter.future; + } + + private void mainThread() { + try { + final Writer writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8); + while (true) { + final RpcMessage message = mainThreadQueue.take(); + final RpcMessage toSend; + if (message.isForSending) { + toSend = message; + } else { + final Function handler = handlers.get(message.method); + if (handler == null) { + System.err.println("Unknown theseus RPC method " + message.method); + continue; + } + RpcMessage response; + try { + response = new RpcMessage(message.id, handler.apply(message.args)); + } catch (Exception e) { + response = new RpcMessage(message.id, e.toString()); + } + toSend = response; + } + GSON.toJson(toSend, writer); + writer.write('\n'); + writer.flush(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (InterruptedException ignored) { + } + } + + private void readThread() { + try { + final BufferedReader reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + while (true) { + final RpcMessage message = GSON.fromJson(reader.readLine(), MESSAGE_TYPE); + if (message.method == null) { + final ResponseWaiter waiter = awaitingResponse.get(message.id); + if (waiter != null) { + handleResponse(waiter, message); + } + } else { + mainThreadQueue.put(message); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (InterruptedException ignored) { + } + } + + private void handleResponse(ResponseWaiter waiter, RpcMessage message) { + if (message.error != null) { + waiter.future.completeExceptionally(new RpcMethodException(message.error)); + return; + } + try { + waiter.future.complete(GSON.fromJson(message.response, waiter.type)); + } catch (JsonSyntaxException e) { + waiter.future.completeExceptionally(e); + } + } + + private static class RpcMessage { + final UUID id; + final String method; // Optional + final JsonElement[] args; // Optional + final JsonElement response; // Optional + final String error; // Optional + final transient boolean isForSending; + + RpcMessage(String method, JsonElement[] args) { + id = UUID.randomUUID(); + this.method = method; + this.args = args; + response = null; + error = null; + isForSending = true; + } + + RpcMessage(UUID id, JsonElement response) { + this.id = id; + method = null; + args = null; + this.response = response; + error = null; + isForSending = true; + } + + RpcMessage(UUID id, String error) { + this.id = id; + method = null; + args = null; + response = null; + this.error = error; + isForSending = true; + } + } + + private static class ResponseWaiter { + final TypeToken type; + final CompletableFuture future = new CompletableFuture<>(); + + ResponseWaiter(TypeToken type) { + this.type = type; + } + } +} diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs index b173d035..020afbe4 100644 --- a/packages/app-lib/src/api/mod.rs +++ b/packages/app-lib/src/api/mod.rs @@ -35,6 +35,9 @@ pub mod prelude { jre, metadata, minecraft_auth, mr_auth, pack, process, profile::{self, Profile, create}, settings, - util::io::{IOError, canonicalize}, + util::{ + io::{IOError, canonicalize}, + network::tcp_listen_any_loopback, + }, }; } diff --git a/packages/app-lib/src/error.rs b/packages/app-lib/src/error.rs index 75c144f5..773d55da 100644 --- a/packages/app-lib/src/error.rs +++ b/packages/app-lib/src/error.rs @@ -151,6 +151,9 @@ pub enum ErrorKind { "A skin texture must have a dimension of either 64x64 or 64x32 pixels" )] InvalidSkinTexture, + + #[error("RPC error: {0}")] + RpcError(String), } #[derive(Debug)] diff --git a/packages/app-lib/src/launcher/args.rs b/packages/app-lib/src/launcher/args.rs index 350d67c0..e2093f61 100644 --- a/packages/app-lib/src/launcher/args.rs +++ b/packages/app-lib/src/launcher/args.rs @@ -16,6 +16,7 @@ use daedalus::{ use dunce::canonicalize; use hashlink::LinkedHashSet; use std::io::{BufRead, BufReader}; +use std::net::SocketAddr; use std::{collections::HashMap, path::Path}; use uuid::Uuid; @@ -124,6 +125,7 @@ pub fn get_jvm_arguments( quick_play_type: &QuickPlayType, quick_play_version: QuickPlayVersion, log_config: Option<&LoggingConfiguration>, + ipc_addr: SocketAddr, ) -> crate::Result> { let mut parsed_arguments = Vec::new(); @@ -181,6 +183,11 @@ pub fn get_jvm_arguments( .to_string_lossy() )); + parsed_arguments + .push(format!("-Dmodrinth.internal.ipc.host={}", ipc_addr.ip())); + parsed_arguments + .push(format!("-Dmodrinth.internal.ipc.port={}", ipc_addr.port())); + parsed_arguments.push(format!( "-Dmodrinth.internal.quickPlay.serverVersion={}", serde_json::to_value(quick_play_version.server)? diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index 64eb1d90..1b7a7d7e 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -12,6 +12,7 @@ use crate::state::{ Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage, }; use crate::util::io; +use crate::util::rpc::RpcServerBuilder; use crate::{State, get_resource_file, process, state as st}; use chrono::Utc; use daedalus as d; @@ -22,7 +23,6 @@ use serde::Deserialize; use st::Profile; use std::fmt::Write; use std::path::PathBuf; -use tokio::io::AsyncWriteExt; use tokio::process::Command; mod args; @@ -608,6 +608,8 @@ pub async fn launch_minecraft( let (main_class_keep_alive, main_class_path) = get_resource_file!(env "JAVA_JARS_DIR" / "theseus.jar")?; + let rpc_server = RpcServerBuilder::new().launch().await?; + command.args( args::get_jvm_arguments( args.get(&d::minecraft::ArgumentType::Jvm) @@ -633,6 +635,7 @@ pub async fn launch_minecraft( .logging .as_ref() .and_then(|x| x.get(&LoggingSide::Client)), + rpc_server.address(), )? .into_iter(), ); @@ -767,7 +770,8 @@ pub async fn launch_minecraft( state.directories.profile_logs_dir(&profile.path), version_info.logging.is_some(), main_class_keep_alive, - async |process: &ProcessMetadata, stdin| { + rpc_server, + async |process: &ProcessMetadata, rpc_server| { let process_start_time = process.start_time.to_rfc3339(); let profile_created_time = profile.created.to_rfc3339(); let profile_modified_time = profile.modified.to_rfc3339(); @@ -790,14 +794,11 @@ pub async fn launch_minecraft( let Some(value) = value else { continue; }; - stdin.write_all(b"property\t").await?; - stdin.write_all(key.as_bytes()).await?; - stdin.write_u8(b'\t').await?; - stdin.write_all(value.as_bytes()).await?; - stdin.write_u8(b'\n').await?; + rpc_server + .call_method_2::<()>("set_system_property", key, value) + .await?; } - stdin.write_all(b"launch\n").await?; - stdin.flush().await?; + rpc_server.call_method::<()>("launch").await?; Ok(()) }, ) diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index faf1c9b4..4cff0a33 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -2,6 +2,7 @@ use crate::event::emit::{emit_process, emit_profile}; use crate::event::{ProcessPayloadType, ProfilePayloadType}; use crate::profile; use crate::util::io::IOError; +use crate::util::rpc::RpcServer; use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; use dashmap::DashMap; use quick_xml::Reader; @@ -15,7 +16,7 @@ use std::path::{Path, PathBuf}; use std::process::ExitStatus; use tempfile::TempDir; use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::{Child, ChildStdin, Command}; +use tokio::process::{Child, Command}; use uuid::Uuid; const LAUNCHER_LOG_PATH: &str = "launcher_log.txt"; @@ -46,9 +47,10 @@ impl ProcessManager { logs_folder: PathBuf, xml_logging: bool, main_class_keep_alive: TempDir, + rpc_server: RpcServer, post_process_init: impl AsyncFnOnce( &ProcessMetadata, - &mut ChildStdin, + &RpcServer, ) -> crate::Result<()>, ) -> crate::Result { mc_command.stdout(std::process::Stdio::piped()); @@ -67,14 +69,12 @@ impl ProcessManager { profile_path: profile_path.to_string(), }, child: mc_proc, + rpc_server, _main_class_keep_alive: main_class_keep_alive, }; - if let Err(e) = post_process_init( - &process.metadata, - &mut process.child.stdin.as_mut().unwrap(), - ) - .await + if let Err(e) = + post_process_init(&process.metadata, &process.rpc_server).await { tracing::error!("Failed to run post-process init: {e}"); let _ = process.child.kill().await; @@ -165,6 +165,10 @@ impl ProcessManager { self.processes.get(&id).map(|x| x.metadata.clone()) } + pub fn get_rpc(&self, id: Uuid) -> Option { + self.processes.get(&id).map(|x| x.rpc_server.clone()) + } + pub fn get_all(&self) -> Vec { self.processes .iter() @@ -215,6 +219,7 @@ struct Process { metadata: ProcessMetadata, child: Child, _main_class_keep_alive: TempDir, + rpc_server: RpcServer, } #[derive(Debug, Default)] diff --git a/packages/app-lib/src/util/mod.rs b/packages/app-lib/src/util/mod.rs index 67c5ede1..7656b4a0 100644 --- a/packages/app-lib/src/util/mod.rs +++ b/packages/app-lib/src/util/mod.rs @@ -2,6 +2,8 @@ pub mod fetch; pub mod io; pub mod jre; +pub mod network; pub mod platform; pub mod protocol_version; +pub mod rpc; pub mod server_ping; diff --git a/packages/app-lib/src/util/network.rs b/packages/app-lib/src/util/network.rs new file mode 100644 index 00000000..2837516c --- /dev/null +++ b/packages/app-lib/src/util/network.rs @@ -0,0 +1,17 @@ +use std::io; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use tokio::net::TcpListener; + +pub async fn tcp_listen_any_loopback() -> io::Result { + // IPv4 is tried first for the best compatibility and performance with most systems. + // IPv6 is also tried in case IPv4 is not available. Resolving "localhost" is avoided + // to prevent failures deriving from improper name resolution setup. Any available + // ephemeral port is used to prevent conflicts with other services. This is all as per + // RFC 8252's recommendations + const ANY_LOOPBACK_SOCKET: &[SocketAddr] = &[ + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0), + SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0), + ]; + + TcpListener::bind(ANY_LOOPBACK_SOCKET).await +} diff --git a/packages/app-lib/src/util/rpc.rs b/packages/app-lib/src/util/rpc.rs new file mode 100644 index 00000000..d6902bd8 --- /dev/null +++ b/packages/app-lib/src/util/rpc.rs @@ -0,0 +1,258 @@ +use crate::prelude::tcp_listen_any_loopback; +use crate::{ErrorKind, Result}; +use futures::{SinkExt, StreamExt}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use tokio::net::TcpListener; +use tokio::sync::{mpsc, oneshot}; +use tokio::task::AbortHandle; +use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError}; +use uuid::Uuid; + +type HandlerFuture = Pin>>>; +type HandlerMethod = Box) -> HandlerFuture>; +type HandlerMap = HashMap<&'static str, HandlerMethod>; +type WaitingResponsesMap = + Arc>>>>; + +pub struct RpcServerBuilder { + handlers: HandlerMap, +} + +impl RpcServerBuilder { + pub fn new() -> Self { + Self { + handlers: HashMap::new(), + } + } + + // We'll use this function in the future. Please remove this #[allow] when we do. + #[allow(dead_code)] + pub fn handler( + mut self, + function_name: &'static str, + handler: HandlerMethod, + ) -> Self { + self.handlers.insert(function_name, Box::new(handler)); + self + } + + pub async fn launch(self) -> Result { + let socket = tcp_listen_any_loopback().await?; + let address = socket.local_addr()?; + let (message_sender, message_receiver) = mpsc::unbounded_channel(); + let waiting_responses = Arc::new(Mutex::new(HashMap::new())); + + let join_handle = { + let waiting_responses = waiting_responses.clone(); + tokio::spawn(async move { + let mut server = RunningRpcServer { + message_receiver, + handlers: self.handlers, + waiting_responses: waiting_responses.clone(), + }; + if let Err(e) = server.run(socket).await { + tracing::error!("Failed to run RPC server: {e}"); + } + waiting_responses.lock().unwrap().clear(); + }) + }; + Ok(RpcServer { + address, + message_sender, + waiting_responses, + abort_handle: join_handle.abort_handle(), + }) + } +} + +#[derive(Debug, Clone)] +pub struct RpcServer { + address: SocketAddr, + message_sender: mpsc::UnboundedSender, + waiting_responses: WaitingResponsesMap, + abort_handle: AbortHandle, +} + +impl RpcServer { + pub fn address(&self) -> SocketAddr { + self.address + } + + pub async fn call_method( + &self, + method: &str, + ) -> Result { + self.call_method_any(method, vec![]).await + } + + pub async fn call_method_2( + &self, + method: &str, + arg1: impl Serialize, + arg2: impl Serialize, + ) -> Result { + self.call_method_any( + method, + vec![serde_json::to_value(arg1)?, serde_json::to_value(arg2)?], + ) + .await + } + + async fn call_method_any( + &self, + method: &str, + args: Vec, + ) -> Result { + if self.message_sender.is_closed() { + return Err(ErrorKind::RpcError( + "RPC connection closed".to_string(), + ) + .into()); + } + + let id = Uuid::new_v4(); + let (send, recv) = oneshot::channel(); + self.waiting_responses.lock().unwrap().insert(id, send); + + let message = RpcMessage { + id, + body: RpcMessageBody::Call { + method: method.to_owned(), + args, + }, + }; + if self.message_sender.send(message).is_err() { + self.waiting_responses.lock().unwrap().remove(&id); + return Err(ErrorKind::RpcError( + "RPC connection closed while sending".to_string(), + ) + .into()); + } + + tracing::debug!("Waiting on result for {id}"); + let Ok(result) = recv.await else { + self.waiting_responses.lock().unwrap().remove(&id); + return Err(ErrorKind::RpcError( + "RPC connection closed while waiting for response".to_string(), + ) + .into()); + }; + result.and_then(|x| Ok(serde_json::from_value(x)?)) + } +} + +impl Drop for RpcServer { + fn drop(&mut self) { + self.abort_handle.abort(); + } +} + +struct RunningRpcServer { + message_receiver: mpsc::UnboundedReceiver, + handlers: HandlerMap, + waiting_responses: WaitingResponsesMap, +} + +impl RunningRpcServer { + async fn run(&mut self, listener: TcpListener) -> Result<()> { + let (socket, _) = listener.accept().await?; + drop(listener); + + let mut socket = LinesCodec::new().framed(socket); + loop { + let to_send = tokio::select! { + message = self.message_receiver.recv() => { + if message.is_none() { + break; + } + message + }, + message = socket.next() => { + let message: RpcMessage = match message { + None => break, + Some(Ok(message)) => serde_json::from_str(&message)?, + Some(Err(LinesCodecError::Io(e))) => Err(e)?, + Some(Err(LinesCodecError::MaxLineLengthExceeded)) => unreachable!(), + }; + self.handle_message(message).await? + }, + }; + if let Some(message) = to_send { + let json = serde_json::to_string(&message)?; + match socket.send(json).await { + Ok(()) => {} + Err(LinesCodecError::Io(e)) => Err(e)?, + Err(LinesCodecError::MaxLineLengthExceeded) => { + unreachable!() + } + }; + } + } + Ok(()) + } + + async fn handle_message( + &self, + message: RpcMessage, + ) -> Result> { + if let RpcMessageBody::Call { method, args } = message.body { + let response = match self.handlers.get(method.as_str()) { + Some(handler) => match handler(args).await { + Ok(result) => RpcMessageBody::Respond { response: result }, + Err(e) => RpcMessageBody::Error { + error: e.to_string(), + }, + }, + None => RpcMessageBody::Error { + error: format!("Unknown theseus RPC method {method}"), + }, + }; + Ok(Some(RpcMessage { + id: message.id, + body: response, + })) + } else if let Some(sender) = + self.waiting_responses.lock().unwrap().remove(&message.id) + { + let _ = sender.send(match message.body { + RpcMessageBody::Respond { response } => Ok(response), + RpcMessageBody::Error { error } => { + Err(ErrorKind::RpcError(error).into()) + } + _ => unreachable!(), + }); + Ok(None) + } else { + Ok(None) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct RpcMessage { + id: Uuid, + #[serde(flatten)] + body: RpcMessageBody, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +enum RpcMessageBody { + Call { + method: String, + args: Vec, + }, + Respond { + #[serde(default, skip_serializing_if = "Value::is_null")] + response: Value, + }, + Error { + error: String, + }, +} From 0bc65024433dacc679a1f1190134516f238db1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= <7822554+AlexTMjugador@users.noreply.github.com> Date: Thu, 14 Aug 2025 03:16:36 +0200 Subject: [PATCH 43/59] App surveys (#3605) * feat: surveys * make assigned and dismissed users fields optional * fix: set required CSP sources for Tally forms to show up * make only attempt on windows, temp bypass requirements * fix: lint issues * Add prompt for survey prior to popup * lint * hide ads when survey is open --------- Signed-off-by: Cal H. Co-authored-by: Prospector Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: Cal H. Co-authored-by: IMB11 --- apps/app-frontend/index.html | 1 + apps/app-frontend/src/App.vue | 164 ++++++++++++++++++++++++- apps/app/tauri.conf.json | 4 +- packages/assets/generated-icons.ts | 2 + packages/assets/icons/notepad-text.svg | 1 + 5 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 packages/assets/icons/notepad-text.svg diff --git a/apps/app-frontend/index.html b/apps/app-frontend/index.html index 891f575f..e5549f28 100644 --- a/apps/app-frontend/index.html +++ b/apps/app-frontend/index.html @@ -11,6 +11,7 @@
+ diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 6d2451c3..cb1ea25e 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -18,12 +18,13 @@ import RunningAppBar from '@/components/ui/RunningAppBar.vue' import SplashScreen from '@/components/ui/SplashScreen.vue' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import { useCheckDisableMouseover } from '@/composables/macCssFix.js' -import { hide_ads_window, init_ads_window } from '@/helpers/ads.js' +import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js' import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics' import { get_user } from '@/helpers/cache.js' import { command_listener, warning_listener } from '@/helpers/events.js' import { useFetch } from '@/helpers/fetch.js' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js' +import { list } from '@/helpers/profile.js' import { get } from '@/helpers/settings.ts' import { get_opening_command, initialize_state } from '@/helpers/state' import { getOS, isDev, restartApp } from '@/helpers/utils.js' @@ -43,6 +44,7 @@ import { MaximizeIcon, MinimizeIcon, NewspaperIcon, + NotepadTextIcon, PlusIcon, RestoreIcon, RightArrowIcon, @@ -67,6 +69,7 @@ import { openUrl } from '@tauri-apps/plugin-opener' import { type } from '@tauri-apps/plugin-os' import { check } from '@tauri-apps/plugin-updater' import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state' +import { $fetch } from 'ofetch' import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue' import { RouterView, useRoute, useRouter } from 'vue-router' import { create_profile_and_install_from_file } from './helpers/pack' @@ -81,6 +84,7 @@ provideNotificationManager(notificationManager) const { handleError, addNotification } = notificationManager const news = ref([]) +const availableSurvey = ref(false) const urlModal = ref(null) @@ -225,6 +229,12 @@ async function setupApp() { } catch (error) { console.warn('Failed to generate skin previews in app setup.', error) } + + if (osType === 'windows') { + await processPendingSurveys() + } else { + console.info('Skipping user surveys on non-Windows platforms') + } } const stateFailed = ref(false) @@ -412,6 +422,116 @@ function handleAuxClick(e) { e.target.dispatchEvent(event) } } + +function cleanupOldSurveyDisplayData() { + const threeWeeksAgo = new Date() + threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21) + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + + if (key.startsWith('survey-') && key.endsWith('-display')) { + const dateValue = new Date(localStorage.getItem(key)) + if (dateValue < threeWeeksAgo) { + localStorage.removeItem(key) + } + } + } +} + +async function openSurvey() { + if (!availableSurvey.value) { + console.error('No survey to open') + return + } + + const creds = await getCreds().catch(handleError) + const userId = creds?.user_id + + const formId = availableSurvey.value.tally_id + + const popupOptions = { + layout: 'modal', + width: 700, + autoClose: 2000, + hideTitle: true, + hiddenFields: { + user_id: userId, + }, + onOpen: () => console.info('Opened user survey'), + onClose: () => { + console.info('Closed user survey') + show_ads_window() + }, + onSubmit: () => console.info('Active user survey submitted'), + } + + try { + hide_ads_window() + if (window.Tally?.openPopup) { + console.info(`Opening Tally popup for user survey (form ID: ${formId})`) + dismissSurvey() + window.Tally.openPopup(formId, popupOptions) + } else { + console.warn('Tally script not yet loaded') + show_ads_window() + } + } catch (e) { + console.error('Error opening Tally popup:', e) + show_ads_window() + } + + console.info(`Found user survey to show with tally_id: ${formId}`) + window.Tally.openPopup(formId, popupOptions) +} + +function dismissSurvey() { + localStorage.setItem(`survey-${availableSurvey.value.id}-display`, new Date()) + availableSurvey.value = undefined +} + +async function processPendingSurveys() { + function isWithinLastTwoWeeks(date) { + const twoWeeksAgo = new Date() + twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14) + return date >= twoWeeksAgo + } + + cleanupOldSurveyDisplayData() + + const creds = await getCreds().catch(handleError) + const userId = creds?.user_id + + const instances = await list().catch(handleError) + const isActivePlayer = + instances.findIndex( + (instance) => + isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created), + ) >= 0 + + let surveys = [] + try { + surveys = await $fetch('https://api.modrinth.com/v2/surveys') + } catch (e) { + console.error('Error fetching surveys:', e) + } + + const surveyToShow = surveys.find( + (survey) => + !!( + localStorage.getItem(`survey-${survey.id}-display`) === null && + survey.type === 'tally_app' && + ((survey.condition === 'active_player' && isActivePlayer) || + (survey.assigned_users?.includes(userId) && !survey.dismissed_users?.includes(userId))) + ), + ) + + if (surveyToShow) { + availableSurvey.value = surveyToShow + } else { + console.info('No user survey to show') + } +} - - + +
+ + + diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index d760df6d..4cc577cd 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -1,64 +1,63 @@ { - "name": "@modrinth/app-frontend", - "private": true, - "version": "1.0.0-local", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc --noEmit && vite build", - "tsc:check": "vue-tsc --noEmit", - "lint": "eslint . && prettier --check .", - "fix": "eslint . --fix && prettier --write .", - "intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", - "test": "vue-tsc --noEmit" - }, - "dependencies": { - "@geometrically/minecraft-motd-parser": "^1.1.4", - "@modrinth/assets": "workspace:*", - "@modrinth/ui": "workspace:*", - "@modrinth/utils": "workspace:*", - "@sentry/vue": "^8.27.0", - "@tauri-apps/api": "^2.5.0", - "@tauri-apps/plugin-dialog": "^2.2.1", - "@tauri-apps/plugin-http": "^2.5.0", - "@tauri-apps/plugin-opener": "^2.2.6", - "@tauri-apps/plugin-os": "^2.2.1", - "@tauri-apps/plugin-updater": "^2.7.1", - "@tauri-apps/plugin-window-state": "^2.2.2", - "@types/three": "^0.172.0", - "@vintl/vintl": "^4.4.1", - "@vueuse/core": "^11.1.0", - "dayjs": "^1.11.10", - "floating-vue": "^5.2.2", - "ofetch": "^1.3.4", - "pinia": "^2.1.7", - "posthog-js": "^1.158.2", - "three": "^0.172.0", - "vite-svg-loader": "^5.1.0", - "vue": "^3.5.13", - "vue-multiselect": "3.0.0", - "vue-router": "4.3.0", - "vue-virtual-scroller": "v2.0.0-beta.8" - }, - "devDependencies": { - "@eslint/compat": "^1.1.1", - "@formatjs/cli": "^6.2.12", - "@nuxt/eslint-config": "^0.5.6", - "@taijased/vue-render-tracker": "^1.0.7", - "@vitejs/plugin-vue": "^5.0.4", - "autoprefixer": "^10.4.19", - "eslint": "^9.9.1", - "eslint-config-custom": "workspace:*", - "eslint-plugin-turbo": "^2.5.4", - "postcss": "^8.4.39", - "prettier": "^3.2.5", - "sass": "^1.74.1", - "tailwindcss": "^3.4.4", - "tsconfig": "workspace:*", - "typescript": "^5.5.4", - "vite": "^5.4.6", - "vue-tsc": "^2.1.6" - }, - "packageManager": "pnpm@9.4.0", - "web-types": "../../web-types.json" + "name": "@modrinth/app-frontend", + "private": true, + "version": "1.0.0-local", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "tsc:check": "vue-tsc --noEmit", + "lint": "eslint . && prettier --check .", + "fix": "eslint . --fix && prettier --write .", + "intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", + "test": "vue-tsc --noEmit" + }, + "dependencies": { + "@geometrically/minecraft-motd-parser": "^1.1.4", + "@modrinth/assets": "workspace:*", + "@modrinth/ui": "workspace:*", + "@modrinth/utils": "workspace:*", + "@sentry/vue": "^8.27.0", + "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-dialog": "^2.2.1", + "@tauri-apps/plugin-http": "^2.5.0", + "@tauri-apps/plugin-opener": "^2.2.6", + "@tauri-apps/plugin-os": "^2.2.1", + "@tauri-apps/plugin-updater": "^2.7.1", + "@tauri-apps/plugin-window-state": "^2.2.2", + "@types/three": "^0.172.0", + "@vintl/vintl": "^4.4.1", + "@vueuse/core": "^11.1.0", + "dayjs": "^1.11.10", + "floating-vue": "^5.2.2", + "ofetch": "^1.3.4", + "pinia": "^2.1.7", + "posthog-js": "^1.158.2", + "three": "^0.172.0", + "vite-svg-loader": "^5.1.0", + "vue": "^3.5.13", + "vue-multiselect": "3.0.0", + "vue-router": "4.3.0", + "vue-virtual-scroller": "v2.0.0-beta.8" + }, + "devDependencies": { + "@modrinth/tooling-config": "workspace:*", + "@eslint/compat": "^1.1.1", + "@formatjs/cli": "^6.2.12", + "@nuxt/eslint-config": "^0.5.6", + "@taijased/vue-render-tracker": "^1.0.7", + "@vitejs/plugin-vue": "^5.0.4", + "autoprefixer": "^10.4.19", + "eslint": "^9.9.1", + "eslint-plugin-turbo": "^2.5.4", + "postcss": "^8.4.39", + "prettier": "^3.2.5", + "sass": "^1.74.1", + "tailwindcss": "^3.4.4", + "typescript": "^5.5.4", + "vite": "^5.4.6", + "vue-tsc": "^2.1.6" + }, + "packageManager": "pnpm@9.4.0", + "web-types": "../../web-types.json" } diff --git a/apps/app-frontend/postcss.config.js b/apps/app-frontend/postcss.config.js index 2e7af2b7..1a526247 100644 --- a/apps/app-frontend/postcss.config.js +++ b/apps/app-frontend/postcss.config.js @@ -1,6 +1,6 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, } diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index cb1ea25e..11cc6600 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -1,4 +1,46 @@ diff --git a/apps/app-frontend/src/assets/external/index.js b/apps/app-frontend/src/assets/external/index.js index c20eb57c..479cf4fd 100644 --- a/apps/app-frontend/src/assets/external/index.js +++ b/apps/app-frontend/src/assets/external/index.js @@ -1,18 +1,18 @@ +export { default as ATLauncherIcon } from './atlauncher.svg' export { default as BuyMeACoffeeIcon } from './bmac.svg' export { default as DiscordIcon } from './discord.svg' +export { default as GDLauncherIcon } from './gdlauncher.png' +export { default as GithubIcon } from './github.svg' +export { default as GitLabIcon } from './gitlab.svg' +export { default as GoogleIcon } from './google.svg' export { default as KoFiIcon } from './kofi.svg' +export { default as MastodonIcon } from './mastodon.svg' +export { default as MicrosoftIcon } from './microsoft.svg' +export { default as MultiMCIcon } from './multimc.webp' +export { default as OpenCollectiveIcon } from './opencollective.svg' export { default as PatreonIcon } from './patreon.svg' export { default as PaypalIcon } from './paypal.svg' -export { default as OpenCollectiveIcon } from './opencollective.svg' -export { default as TwitterIcon } from './twitter.svg' -export { default as GithubIcon } from './github.svg' -export { default as MastodonIcon } from './mastodon.svg' -export { default as RedditIcon } from './reddit.svg' -export { default as GoogleIcon } from './google.svg' -export { default as MicrosoftIcon } from './microsoft.svg' -export { default as SteamIcon } from './steam.svg' -export { default as GitLabIcon } from './gitlab.svg' -export { default as ATLauncherIcon } from './atlauncher.svg' -export { default as GDLauncherIcon } from './gdlauncher.png' -export { default as MultiMCIcon } from './multimc.webp' export { default as PrismIcon } from './prism.svg' +export { default as RedditIcon } from './reddit.svg' +export { default as SteamIcon } from './steam.svg' +export { default as TwitterIcon } from './twitter.svg' diff --git a/apps/app-frontend/src/assets/icons/index.js b/apps/app-frontend/src/assets/icons/index.js index 4a5266b2..256b61af 100644 --- a/apps/app-frontend/src/assets/icons/index.js +++ b/apps/app-frontend/src/assets/icons/index.js @@ -1,9 +1,9 @@ -export { default as SwapIcon } from './arrow-left-right.svg' -export { default as ToggleIcon } from './toggle.svg' -export { default as PackageIcon } from './package.svg' -export { default as VersionIcon } from './milestone.svg' -export { default as TextInputIcon } from './text-cursor-input.svg' export { default as AddProjectImage } from './add-project.svg' -export { default as NewInstanceImage } from './new-instance.svg' +export { default as SwapIcon } from './arrow-left-right.svg' export { default as MenuIcon } from './menu.svg' export { default as ChatIcon } from './messages-square.svg' +export { default as VersionIcon } from './milestone.svg' +export { default as NewInstanceImage } from './new-instance.svg' +export { default as PackageIcon } from './package.svg' +export { default as TextInputIcon } from './text-cursor-input.svg' +export { default as ToggleIcon } from './toggle.svg' diff --git a/apps/app-frontend/src/assets/stylesheets/global.scss b/apps/app-frontend/src/assets/stylesheets/global.scss index 7ab08fd5..2969d3cb 100644 --- a/apps/app-frontend/src/assets/stylesheets/global.scss +++ b/apps/app-frontend/src/assets/stylesheets/global.scss @@ -3,158 +3,158 @@ @tailwind utilities; @font-face { - font-family: 'bundled-minecraft-font-mrapp'; - font-style: normal; - font-display: swap; - font-weight: 400; - src: url('https://cdn-raw.modrinth.com/fonts/minecraft/regular.otf') format('opentype'); + font-family: 'bundled-minecraft-font-mrapp'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('https://cdn-raw.modrinth.com/fonts/minecraft/regular.otf') format('opentype'); } @font-face { - font-family: 'bundled-minecraft-font-mrapp'; - font-style: italic; - font-display: swap; - font-weight: 400; - src: url('https://cdn-raw.modrinth.com/fonts/minecraft/italic.otf') format('opentype'); + font-family: 'bundled-minecraft-font-mrapp'; + font-style: italic; + font-display: swap; + font-weight: 400; + src: url('https://cdn-raw.modrinth.com/fonts/minecraft/italic.otf') format('opentype'); } @font-face { - font-family: 'bundled-minecraft-font-mrapp'; - font-style: normal; - font-display: swap; - font-weight: 600; - src: url('https://cdn-raw.modrinth.com/fonts/minecraft/bold.otf') format('opentype'); + font-family: 'bundled-minecraft-font-mrapp'; + font-style: normal; + font-display: swap; + font-weight: 600; + src: url('https://cdn-raw.modrinth.com/fonts/minecraft/bold.otf') format('opentype'); } @font-face { - font-family: 'bundled-minecraft-font-mrapp'; - font-style: italic; - font-display: swap; - font-weight: 600; - src: url('https://cdn-raw.modrinth.com/fonts/minecraft/bold-italic.otf') format('opentype'); + font-family: 'bundled-minecraft-font-mrapp'; + font-style: italic; + font-display: swap; + font-weight: 600; + src: url('https://cdn-raw.modrinth.com/fonts/minecraft/bold-italic.otf') format('opentype'); } .font-minecraft { - font-family: 'bundled-minecraft-font-mrapp', monospace; + font-family: 'bundled-minecraft-font-mrapp', monospace; } :root { - font-family: var(--font-standard, sans-serif), sans-serif; - color-scheme: dark; - --view-width: calc(100% - 5rem); - --expanded-view-width: calc(100% - 13rem); + font-family: var(--font-standard, sans-serif), sans-serif; + color-scheme: dark; + --view-width: calc(100% - 5rem); + --expanded-view-width: calc(100% - 13rem); } body { - position: fixed; - width: 100%; - height: 100%; - overflow: hidden; + position: fixed; + width: 100%; + height: 100%; + overflow: hidden; } * { - box-sizing: border-box; + box-sizing: border-box; } .card-divider { - background-color: var(--color-button-bg); - border: none; - color: var(--color-button-bg); - height: 1px; - margin: var(--gap-sm) 0; + background-color: var(--color-button-bg); + border: none; + color: var(--color-button-bg); + height: 1px; + margin: var(--gap-sm) 0; } .no-wrap { - white-space: nowrap; + white-space: nowrap; } .no-select { - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } a { - color: var(--color-link); - text-decoration: none; + color: var(--color-link); + text-decoration: none; - &:hover { - text-decoration: none; - } + &:hover { + text-decoration: none; + } } input { - border: none !important; + border: none !important; } .badge { - display: flex; - border-radius: var(--radius-md); - white-space: nowrap; - align-items: center; - background-color: var(--color-bg); - padding-block: var(--gap-sm); - padding-inline: var(--gap-lg); - width: min-content; + display: flex; + border-radius: var(--radius-md); + white-space: nowrap; + align-items: center; + background-color: var(--color-bg); + padding-block: var(--gap-sm); + padding-inline: var(--gap-lg); + width: min-content; - svg { - width: 1.1rem; - height: 1.1rem; - margin-right: 0.5rem; - } + svg { + width: 1.1rem; + height: 1.1rem; + margin-right: 0.5rem; + } - &.featured { - background-color: var(--color-brand-highlight); - color: var(--color-contrast); - } + &.featured { + background-color: var(--color-brand-highlight); + color: var(--color-contrast); + } } * { - scrollbar-width: auto; - scrollbar-color: var(--color-scrollbar) var(--color-bg); + scrollbar-width: auto; + scrollbar-color: var(--color-scrollbar) var(--color-bg); } /* Chrome, Edge, and Safari */ *::-webkit-scrollbar { - width: 16px; - border: 3px solid transparent; - opacity: 0.5; - transition: opacity 0.2s ease-in-out; + width: 16px; + border: 3px solid transparent; + opacity: 0.5; + transition: opacity 0.2s ease-in-out; } *::-webkit-scrollbar:hover { - opacity: 1; + opacity: 1; } *::-webkit-scrollbar-track { - background: transparent; + background: transparent; } *::-webkit-scrollbar-thumb { - background-color: var(--color-scrollbar); - border-radius: var(--radius-lg); - border: 5px solid transparent; - background-clip: content-box; + background-color: var(--color-scrollbar); + border-radius: var(--radius-lg); + border: 5px solid transparent; + background-clip: content-box; } .highlighted { - box-shadow: 0 0 1rem var(--color-brand) !important; + box-shadow: 0 0 1rem var(--color-brand) !important; } .gecko { - background-color: var(--color-raised-bg); - box-shadow: none !important; + background-color: var(--color-raised-bg); + box-shadow: none !important; } img { - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; } .card-shadow { - box-shadow: var(--shadow-card); + box-shadow: var(--shadow-card); } @import '@modrinth/assets/omorphia.scss'; diff --git a/apps/app-frontend/src/assets/stylesheets/macFix.css b/apps/app-frontend/src/assets/stylesheets/macFix.css index b56737a9..901ce864 100644 --- a/apps/app-frontend/src/assets/stylesheets/macFix.css +++ b/apps/app-frontend/src/assets/stylesheets/macFix.css @@ -1,3 +1,3 @@ img { - pointer-events: none !important; + pointer-events: none !important; } diff --git a/apps/app-frontend/src/components/GridDisplay.vue b/apps/app-frontend/src/components/GridDisplay.vue index 896c55b9..046244f6 100644 --- a/apps/app-frontend/src/components/GridDisplay.vue +++ b/apps/app-frontend/src/components/GridDisplay.vue @@ -1,37 +1,38 @@ diff --git a/apps/app-frontend/src/components/LoadingIndicatorBar.vue b/apps/app-frontend/src/components/LoadingIndicatorBar.vue index 3c1dd088..7cbee496 100644 --- a/apps/app-frontend/src/components/LoadingIndicatorBar.vue +++ b/apps/app-frontend/src/components/LoadingIndicatorBar.vue @@ -1,29 +1,30 @@ diff --git a/apps/app-frontend/src/components/RowDisplay.vue b/apps/app-frontend/src/components/RowDisplay.vue index f0a3edbd..b0114653 100644 --- a/apps/app-frontend/src/components/RowDisplay.vue +++ b/apps/app-frontend/src/components/RowDisplay.vue @@ -1,4 +1,21 @@ diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue index f26d8c6f..73d10598 100644 --- a/apps/app-frontend/src/components/ui/AccountsCard.vue +++ b/apps/app-frontend/src/components/ui/AccountsCard.vue @@ -1,102 +1,103 @@ diff --git a/apps/app-frontend/src/components/ui/AddContentButton.vue b/apps/app-frontend/src/components/ui/AddContentButton.vue index d3447bf0..193b6e80 100644 --- a/apps/app-frontend/src/components/ui/AddContentButton.vue +++ b/apps/app-frontend/src/components/ui/AddContentButton.vue @@ -1,61 +1,62 @@ diff --git a/apps/app-frontend/src/components/ui/Breadcrumbs.vue b/apps/app-frontend/src/components/ui/Breadcrumbs.vue index d87c502f..ee12e17e 100644 --- a/apps/app-frontend/src/components/ui/Breadcrumbs.vue +++ b/apps/app-frontend/src/components/ui/Breadcrumbs.vue @@ -1,63 +1,64 @@ diff --git a/apps/app-frontend/src/components/ui/ContextMenu.vue b/apps/app-frontend/src/components/ui/ContextMenu.vue index a0ca9417..bddcbfb8 100644 --- a/apps/app-frontend/src/components/ui/ContextMenu.vue +++ b/apps/app-frontend/src/components/ui/ContextMenu.vue @@ -1,26 +1,26 @@ diff --git a/apps/app-frontend/src/components/ui/ErrorModal.vue b/apps/app-frontend/src/components/ui/ErrorModal.vue index bdd1acf7..17a9dffc 100644 --- a/apps/app-frontend/src/components/ui/ErrorModal.vue +++ b/apps/app-frontend/src/components/ui/ErrorModal.vue @@ -1,4 +1,16 @@ diff --git a/apps/app-frontend/src/components/ui/ExportModal.vue b/apps/app-frontend/src/components/ui/ExportModal.vue index 3957a5ad..d29633af 100644 --- a/apps/app-frontend/src/components/ui/ExportModal.vue +++ b/apps/app-frontend/src/components/ui/ExportModal.vue @@ -1,26 +1,27 @@ diff --git a/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue b/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue index dcbaf417..34ee921e 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/options/startup.vue @@ -1,234 +1,235 @@