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 09e1f4f89..557a75f24 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 557a75f24..3c77addac 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 75f3f2d9f..f15925ae3 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 40600b2f7..c5157ca79 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 5aef00800..81d4a51df 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 000000000..5a3b58461 --- /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 797d11334..ff28901f2 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 7b5240c12..ff721e6cb 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 2f09ed3c1..8804c47a2 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 8708054a8..ee0094440 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 9109822e0..13be399cd 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 f4d512fc1..dd5d3db67 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 96ace83c5..09ccbaf37 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 aa0901fee..a37c90435 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 e6a708f7b..38e72448e 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 6f522ee34..a738d43e2 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 a5fcbc176..49e50eac7 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 7cb644384..662a8b093 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 652a44d2e..6bcdd538f 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 6f795de95..ab8058e0f 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 d11c6b2d3..9f48fc6b8 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 61778eaa0..43d59b76a 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 9d962c98e..f47b06613 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 000000000..0e842fef8 --- /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 308b7e3a9..0a118099c 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 38e72448e..3a5bdd371 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 2bb35162f..1dc0cfd6b 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 35eb72a2c..7b5327ed3 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 4dbb509d6..3de15993c 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 aa4ff5fa7..c344d7cbf 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 d485333d2..000000000 --- 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 af15186e1..000000000 --- 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 000000000..8ec2c4d67 --- /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 000000000..4d375ffeb --- /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 0613982d4..000000000 --- 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 000000000..f18897fc6 --- /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 000000000..40f28feca --- /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 000000000..d89a68fcd --- /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 249a53b60..f01044155 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 000000000..49723c697 --- /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 000000000..ebe30683a --- /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 000000000..338dd8d6f --- /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 000000000..5f7cc2a51 --- /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 000000000..ee0c9dd99 --- /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 000000000..a6bcd943d --- /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 000000000..876dda2bd --- /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 000000000..cc21f5f18 --- /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 000000000..db1fd232a --- /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 9976fa968..7f5ca33a0 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 a7cebdcd5..0ee0afdf4 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 000000000..c05dc348b --- /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 000000000..14d06c839 --- /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 c87ef340b..9843eb152 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 e1217fc59..85b7f2da0 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 a56bc3c56..61f553a20 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 a37c90435..c311ab042 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 dccfba6a7..2c66bdec3 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 9b12a8e9e..caa71614a 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 000000000..52e2fa281 --- /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 000000000..6901e7eb6 --- /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 000000000..8c59da9dd --- /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 ded6925db..78e8366f6 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 d827f9902..57781986e 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 fdd264787..a6467057f 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 18f8c4e68..9157b8301 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 f86ded144..cd8ebc8fe 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 06ba85141..07d83a7da 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 be648f276..e7aad9896 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 78f879669..49986ebb6 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 dec1b9771..080d4deae 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 1ff659c73..ded248ea3 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 76e7c7a9f..106efae08 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 15582bee4..22fe93ed6 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 50f868bad..47b60f5c3 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 51fcab855..6c2cc9b7f 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 9c4fdc277..067e36695 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 842f5b544..9acdaee0d 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 433588624..1300fd7be 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 cb9cd608d..ed82add51 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 8b9704a12..f641e593f 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 2505b8baa..12e012ca2 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 64059f4d5..94c680967 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 e67061385..e16fc8052 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 ad9023cda..fdb9e9103 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 7095cd848..51a9e2871 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 284ffa871..70fafd931 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 475367e1b..6756d8d20 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 be6189801..ffb964637 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 beab75148..f68d142e6 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 6c8dbc53f..7e0d77241 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 4fe699a69..55d32a62c 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 7f5ca33a0..bf8e31963 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 9aafa0588..9feb8c272 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 d456c5b8b..0bb5c7362 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 bd9a7edd4..7080f0e1c 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 000000000..c8859edc1 --- /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 000000000..c56507b0f --- /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 bc3f33d92..ede87cabc 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 4e4a52928..fedffcb09 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 48da4b457..10ed29b99 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 a671dd6f9..98c95c8c9 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 9d61a0c0b..b474ba02c 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 000000000..257148ef5 --- /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 000000000..f9ab75a35 --- /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 000000000..ff460ff89 --- /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 b173d035f..020afbe49 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 75c144f55..773d55daa 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 350d67c0b..e2093f618 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 64eb1d90e..1b7a7d7e0 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 faf1c9b4f..4cff0a33e 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 67c5ede16..7656b4a03 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 000000000..2837516c5 --- /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 000000000..d6902bd85 --- /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 891f575f2..e5549f28a 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 6d2451c31..cb1ea25e4 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 d760df6d3..4cc577cdb 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 2e7af2b7f..1a5262473 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 cb1ea25e4..11cc6600e 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 c20eb57c9..479cf4fd8 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 4a5266b25..256b61af2 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 7ab08fd57..2969d3cb4 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 b56737a9c..901ce8642 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 896c55b98..046244f66 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 3c1dd0883..7cbee4965 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 f0a3edbdd..b01146535 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 f26d8c6f6..73d10598b 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 d3447bf0a..193b6e80f 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 d87c502f5..ee12e17ea 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 a0ca9417e..bddcbfb8b 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 bdd1acf79..17a9dffc7 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 3957a5add..d29633af5 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 dcbaf4172..34ee921e7 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 @@