feat(app): 添加版本更新弹窗功能
- 在顶部导航栏和用户状态组件中集成版本更新弹窗 - 实现版本检查钩子,支持检查最新版本和更新日志 - 解析 CHANGELOG.md 文件并展示版本更新时间线 - 添加版本更新弹窗 UI 组件,显示当前版本和最新版本 - 在 Dockerfile 中更新 bun 版本依赖 - 调整 Next.js 配置以解析版本信息并注入环境变量 - 更新待测试文档中的版本更新功能描述
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { parseChangelog, type ReleaseInfo } from "@/lib/release-info";
|
||||
|
||||
const latestVersionUrl = "https://raw.githubusercontent.com/basketikun/infinite-canvas/main/VERSION";
|
||||
const latestChangelogUrl = "https://raw.githubusercontent.com/basketikun/infinite-canvas/main/CHANGELOG.md";
|
||||
|
||||
function readLocalReleases(): ReleaseInfo[] {
|
||||
try {
|
||||
return JSON.parse(process.env.NEXT_PUBLIC_APP_RELEASES || "[]");
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function toVersionParts(version: string) {
|
||||
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
||||
return match ? match.slice(1).map(Number) : null;
|
||||
}
|
||||
|
||||
function isNewerVersion(latestVersion: string, currentVersion: string) {
|
||||
const latest = toVersionParts(latestVersion);
|
||||
const current = toVersionParts(currentVersion);
|
||||
if (!latest || !current) return false;
|
||||
return latest.some((value, index) => value > current[index] && latest.slice(0, index).every((part, prevIndex) => part === current[prevIndex]));
|
||||
}
|
||||
|
||||
export function useVersionCheck(currentVersion: string) {
|
||||
const localReleases = useMemo(readLocalReleases, []);
|
||||
const [latestVersion, setLatestVersion] = useState(currentVersion);
|
||||
const [releases, setReleases] = useState<ReleaseInfo[]>(localReleases);
|
||||
const [checking, setChecking] = useState(false);
|
||||
const hasNewVersion = isNewerVersion(latestVersion, currentVersion);
|
||||
|
||||
const checkLatestVersion = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(latestVersionUrl);
|
||||
if (!response.ok) return false;
|
||||
const version = await response.text();
|
||||
setLatestVersion(version.trim() || currentVersion);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [currentVersion]);
|
||||
|
||||
const checkLatestRelease = useCallback(async () => {
|
||||
setChecking(true);
|
||||
try {
|
||||
const [versionResponse, changelogResponse] = await Promise.all([
|
||||
fetch(latestVersionUrl),
|
||||
fetch(latestChangelogUrl),
|
||||
]);
|
||||
if (!versionResponse.ok) throw new Error("版本读取失败");
|
||||
if (!changelogResponse.ok) throw new Error("更新日志读取失败");
|
||||
const [version, changelog] = await Promise.all([versionResponse.text(), changelogResponse.text()]);
|
||||
setLatestVersion(version.trim() || currentVersion);
|
||||
if (changelog.trim()) setReleases(parseChangelog(changelog));
|
||||
return true;
|
||||
} catch {
|
||||
setLatestVersion(currentVersion);
|
||||
setReleases(localReleases);
|
||||
return false;
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
}, [currentVersion, localReleases]);
|
||||
|
||||
useEffect(() => {
|
||||
void checkLatestVersion();
|
||||
}, [checkLatestVersion]);
|
||||
|
||||
return { latestVersion, releases, checking, hasNewVersion, checkLatestRelease };
|
||||
}
|
||||
Reference in New Issue
Block a user