From 2d96f280918113d04477e80a71adce8bdd1a0a4c Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Fri, 9 Oct 2026 22:15:55 +0200 Subject: [PATCH] feat(site): redesigned /download, releases and links moved from GitHub to Forgejo - /download: the jar as an in-game item (pixel heart in an inventory slot, vanilla-style tooltip with version, size and SHA-256, copy button), spec list, three install steps, release timeline - releases read through a cached nginx proxy (/api/releases): Forgejo's API has no CORS headers - README, footer and landing links point to git.wihuk.pro/boba/LoVisual (the GitHub repo they used does not exist) - release workflow publishes through the Forgejo API and ships lovisual.jar.sha256 - fix i18n interpolation placeholders ({{x}}) in the download strings --- .github/workflows/release.yml | 35 +++++-- README.md | 12 +-- .../deploy/nginx-lovisual-releases-cache.conf | 2 + .../deploy/nginx-visual.loki-code.dev.conf | 15 +++ .../src/features/download/ReleaseCard.tsx | 77 +++++++++++++++ .../src/features/download/ReleaseNotes.tsx | 42 ++++---- frontend/src/features/download/api.ts | 44 +++++---- frontend/src/features/download/download.css | 79 +++++++++++++++ frontend/src/features/download/i18n/en.ts | 17 +++- frontend/src/features/download/i18n/ru.ts | 23 +++-- .../features/download/tests/download.test.tsx | 38 ++++++-- frontend/src/features/landing/i18n/en.ts | 2 +- frontend/src/features/landing/i18n/ru.ts | 2 +- .../landing/sections/explore/FaqDownload.tsx | 4 +- frontend/src/pages/download/DownloadPage.tsx | 95 +++++++------------ frontend/src/shared/i18n/common.en.ts | 2 +- frontend/src/shared/i18n/common.ru.ts | 2 +- frontend/src/shared/layout/Footer.tsx | 6 +- .../src/shared/layout/tests/layout.test.tsx | 6 +- 19 files changed, 360 insertions(+), 143 deletions(-) create mode 100644 backend/deploy/nginx-lovisual-releases-cache.conf create mode 100644 frontend/src/features/download/ReleaseCard.tsx create mode 100644 frontend/src/features/download/download.css diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2903e84..e3f16509 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up JDK 25 uses: actions/setup-java@v4 @@ -46,11 +48,28 @@ jobs: echo "version=$version" >>"$GITHUB_OUTPUT" echo "Packaged $jar as lovisual-$version.jar and lovisual.jar" - - name: Publish the GitHub release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - generate_release_notes: true - files: | - lovisual-${{ steps.jar.outputs.version }}.jar - lovisual.jar + # The repository lives on Forgejo, so the release goes through its API. + # One-time setup: Settings > Actions > Secrets > RELEASE_TOKEN (an API + # token with write access to this repository's releases). + - name: Publish the Forgejo release + env: + TOKEN: ${{ secrets.RELEASE_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + TAG: ${{ github.ref_name }} + VERSION: ${{ steps.jar.outputs.version }} + run: | + set -euo pipefail + test -n "$TOKEN" || { echo "RELEASE_TOKEN secret is missing"; exit 1; } + sha256sum lovisual.jar > lovisual.jar.sha256 + prev="$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true)" + range="${prev:+$prev..}$TAG" + notes="$(git log --no-merges --pretty='- %s' "$range" | head -60)" + body="$(python3 -c 'import json,sys; print(json.dumps({"tag_name":sys.argv[1],"name":sys.argv[1],"body":sys.argv[2]}))' "$TAG" "$notes")" + id="$(curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \ + -H 'Content-Type: application/json' -d "$body" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + for f in "lovisual-$VERSION.jar" lovisual.jar lovisual.jar.sha256; do + curl -fsS -X POST "$API/releases/$id/assets?name=$f" -H "Authorization: token $TOKEN" \ + -F "attachment=@$f" >/dev/null + echo "uploaded $f" + done diff --git a/README.md b/README.md index a42b6ead..07277201 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ Open-source Minecraft utility client **and** the platform around it - website, c Developed and maintained by [loki5512344](https://github.com/loki5512344). -![Release](https://img.shields.io/github/v/release/loki5512344/LoVisual-?style=flat-square&color=blue) +![Release](https://img.shields.io/gitea/v/release/boba/LoVisual?gitea_url=https%3A%2F%2Fgit.wihuk.pro&style=flat-square&color=blue) ![Java](https://img.shields.io/badge/Java-25+-orange?style=flat-square&logo=openjdk&logoColor=white) ![Rust](https://img.shields.io/badge/Rust-backend-red?style=flat-square&logo=rust&logoColor=white) ![React](https://img.shields.io/badge/Frontend-React%20%2B%20Vite-61dafb?style=flat-square&logo=react&logoColor=white) ![License](https://img.shields.io/badge/license-GPLv3-blue?style=flat-square&logo=gnu&logoColor=white) -**[⬇ Download the mod](https://github.com/loki5512344/LoVisual-/releases/latest/download/lovisual.jar)** · [Releases](https://github.com/loki5512344/LoVisual-/releases) +**[⬇ Download the mod](https://git.wihuk.pro/boba/LoVisual/releases/latest/download/lovisual.jar)** · [Releases](https://git.wihuk.pro/boba/LoVisual/releases) [English](#english) | [Русский](#русский) @@ -83,8 +83,8 @@ cargo build cargo test ``` -Releases are cut by pushing a `v*` tag - GitHub Actions builds the mod jar and publishes it to -[Releases](https://github.com/loki5512344/LoVisual-/releases) as both `lovisual-.jar` +Releases are cut by pushing a `v*` tag - Forgejo Actions builds the mod jar and publishes it to +[Releases](https://git.wihuk.pro/boba/LoVisual/releases) as both `lovisual-.jar` and a stable `lovisual.jar` link. ### License @@ -155,8 +155,8 @@ cargo build cargo test ``` -Релизы создаются пушем тега `v*` - GitHub Actions собирает jar мода и публикует в -[Releases](https://github.com/loki5512344/LoVisual-/releases) как `lovisual-<версия>.jar` +Релизы создаются пушем тега `v*` - Forgejo Actions собирает jar мода и публикует в +[Releases](https://git.wihuk.pro/boba/LoVisual/releases) как `lovisual-<версия>.jar` и по стабильной ссылке `lovisual.jar`. ### Лицензия diff --git a/backend/deploy/nginx-lovisual-releases-cache.conf b/backend/deploy/nginx-lovisual-releases-cache.conf new file mode 100644 index 00000000..19c77f6c --- /dev/null +++ b/backend/deploy/nginx-lovisual-releases-cache.conf @@ -0,0 +1,2 @@ +# Cache zone for the /api/releases proxy (see nginx-visual.loki-code.dev.conf). +proxy_cache_path /var/cache/nginx/lv_releases levels=1 keys_zone=lv_releases:1m max_size=10m inactive=1h use_temp_path=off; diff --git a/backend/deploy/nginx-visual.loki-code.dev.conf b/backend/deploy/nginx-visual.loki-code.dev.conf index bd5384b7..133701b4 100644 --- a/backend/deploy/nginx-visual.loki-code.dev.conf +++ b/backend/deploy/nginx-visual.loki-code.dev.conf @@ -3,6 +3,21 @@ server { root /var/www/visual.loki-code.dev/html; index index.html; + # Forgejo's API sends no CORS headers, so the download page reads the + # release list through this cached proxy (5 min) instead of from the browser. + location = /api/releases { + proxy_pass https://git.wihuk.pro/api/v1/repos/boba/LoVisual/releases?limit=10; + proxy_ssl_server_name on; + proxy_set_header Host git.wihuk.pro; + proxy_set_header Authorization ""; + proxy_set_header Cookie ""; + proxy_hide_header Set-Cookie; + proxy_cache lv_releases; + proxy_cache_valid 200 5m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503; + add_header Cache-Control "public, max-age=60" always; + } + location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_http_version 1.1; diff --git a/frontend/src/features/download/ReleaseCard.tsx b/frontend/src/features/download/ReleaseCard.tsx new file mode 100644 index 00000000..d953b960 --- /dev/null +++ b/frontend/src/features/download/ReleaseCard.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { useTranslation } from '@shared/i18n/react' +import { CHECKSUM_URL, DOWNLOAD_URL, fetchChecksum, formatSize, type Release, releaseSize } from './api' +import './download.css' + +/** Pixel heart from the logo, one rect per cell so it stays crisp at any size. */ +const HEART = ['.XX.XX.', 'XXXXXXX', 'XXXXXXX', '.XXXXX.', '..XXX..', '...X...'] + +function PixelHeart() { + const cells = HEART.flatMap((row, y) => + [...row].flatMap((c, x) => (c === 'X' ? [{ x, y, hi: x + y < 3 && x < 3 }] : [])), + ) + return ( + + ) +} + +/** + * The download as an in-game item: heart in an inventory slot, a vanilla-style + * tooltip with version, size and SHA-256, and the download button underneath. + */ +export function ReleaseCard({ release }: { release?: Release }) { + const { t, i18n } = useTranslation('download') + const [copied, setCopied] = useState(false) + const { data: hash } = useQuery({ queryKey: ['checksum'], queryFn: fetchChecksum, staleTime: 10 * 60 * 1000 }) + const version = release?.tag_name.replace(/^v/, '') + const bytes = release ? releaseSize(release) : 0 + + const copy = () => { + if (!hash) return + void navigator.clipboard?.writeText(hash).then(() => { + setCopied(true) + window.setTimeout(() => setCopied(false), 1800) + }) + } + + return ( +
+
+
+
+
LoVisual{version ? ` ${version}` : ''}
+
{t('card.platform')}
+ {bytes > 0 ?
{t('card.size', { size: formatSize(bytes, i18n.language) })}
: null} + {hash ? ( +
+ SHA-256 +
{hash}
+
+ ) : null} +
+
+
+ {t('button')} + {hash ? ( + + ) : null} + {t('card.checksumFile')} +
+
+ ) +} diff --git a/frontend/src/features/download/ReleaseNotes.tsx b/frontend/src/features/download/ReleaseNotes.tsx index a2a3426c..bae7eaee 100644 --- a/frontend/src/features/download/ReleaseNotes.tsx +++ b/frontend/src/features/download/ReleaseNotes.tsx @@ -2,10 +2,12 @@ import { useQuery } from '@tanstack/react-query' import { useTranslation } from '@shared/i18n/react' import { Markdown } from '@shared/ui/Markdown' import { fetchReleases } from './api' +import './download.css' /** - * Changelog feed. Markdown bodies go through our own renderer, which builds React - * elements and never raw HTML, so a `