Commit 15fd3ece by 周田

fix: 修复公开下载页不显示版本更新内容

公开应用列表的 SQL 中应用介绍与版本说明使用同名列 description,
合并为字典时版本说明被覆盖,前端因此拿不到更新内容。

- 将版本说明列改为 release_description,避免与应用介绍冲突
- 下载页分别渲染“程序介绍”和“版本更新内容”两个区块
- 更新静态资源版本号并补充相应测试断言
parent cacd1900
......@@ -15,7 +15,7 @@ distribution-server/
└─ settings.py
```
管理 API 以 `/api` 开头;创建应用时可填写应用说明。上传版本必须填写 Markdown 更新内容;服务器流式计算文件大小和 SHA-256,并记录文件名、格式、平台、架构、更新内容、上传时间和发布状态。支持 EXE、MSI、ZIP、7Z、DMG、PKG、DEB、RPM、AppImage 和 TAR.GZ。Markdown 说明支持标题、列表、粗体、链接和代码。
管理 API 以 `/api` 开头;创建应用时可填写应用说明,公开下载页会将其标记为“程序介绍”,与各版本的更新内容分开展示。上传版本必须填写 Markdown 更新内容;服务器流式计算文件大小和 SHA-256,并记录文件名、格式、平台、架构、更新内容、上传时间和发布状态。支持 EXE、MSI、ZIP、7Z、DMG、PKG、DEB、RPM、AppImage 和 TAR.GZ。Markdown 说明支持标题、列表、粗体、链接和代码。
版本说明也可以在管理页的程序包记录中单独编辑,无需重新上传程序包;保存后会同时显示在管理记录和公开下载页。
......
......@@ -304,8 +304,8 @@ class DistributionStore:
rows = connection.execute(
"""
SELECT a.app_id, a.display_name, a.description, a.current_version,
r.filename, r.description, r.platform, r.architecture,
r.size, r.sha256,
r.filename, r.description AS release_description,
r.platform, r.architecture, r.size, r.sha256,
r.published_at
FROM applications AS a
JOIN releases AS r
......
......@@ -33,6 +33,8 @@ h1 { margin: 8px 0 7px; font-size: clamp(24px, 4vw, 32px); letter-spacing: -.03e
.package-version { flex: 0 0 auto; padding: 4px 8px; border-radius: 999px; color: #536077; background: #f0f2f6; font-size: 11px; font-weight: 700; }
.package-name { min-width: 0; color: var(--ink); font-size: 18px; font-weight: 700; letter-spacing: -.02em; overflow-wrap: anywhere; }
.package-app-description { margin: 10px 0 0; color: var(--muted); font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
.package-app-description-title, .application-description-title { display: block; color: #536077; font-size: 13px; }
.package-app-description-content { margin: 4px 0 0; }
.package-badge { flex: 0 0 auto; padding: 5px 9px; border-radius: 999px; color: #56647a; background: #f0f2f6; font-size: 10px; font-weight: 700; }
.package-badge-helper { color: #6652b8; background: #f0edff; }
.package-metadata { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 18px 0; padding: 13px 0; border-top: 1px solid #eff1f5; border-bottom: 1px solid #eff1f5; }
......@@ -86,7 +88,8 @@ footer { font-size: 12px; }
.package-description { margin: 14px 0 0; color: #68758a; font-size: 13px; }
.package-description-title { display: block; margin-bottom: 6px; color: #536077; font-size: 13px; }
.package-description-content { padding: 13px 15px; border-radius: 8px; background: #f7f8fc; }
.application-description { margin-top: 8px !important; color: var(--muted); font-size: 15px; line-height: 1.7; overflow-wrap: anywhere; }
.application-description { margin-top: 8px; color: var(--muted); font-size: 15px; line-height: 1.7; overflow-wrap: anywhere; }
.application-description p { margin: 4px 0 0; }
.markdown-body { color: #465268; font-size: 14px; line-height: 1.7; overflow-wrap: anywhere; }
.markdown-body > :first-child { margin-top: 0; }
.markdown-body > :last-child { margin-bottom: 0; }
......
......@@ -39,7 +39,7 @@ function formatSize(bytes) {
function packageCard(
packageInfo,
appName,
{ compact = false, applicationDescription = "" } = {},
{ compact = false, applicationDescription = "", releaseNotes = "" } = {},
) {
const card = node("article", `package-card${compact ? " package-card-compact" : ""}`);
const heading = node("div", "package-heading");
......@@ -56,12 +56,17 @@ function packageCard(
heading.append(identity, badge);
card.append(heading);
if (applicationDescription) {
card.append(node("p", "package-app-description", applicationDescription));
const description = node("section", "package-app-description");
description.append(
node("strong", "package-app-description-title", "程序介绍"),
node("p", "package-app-description-content", applicationDescription),
);
card.append(description);
}
if (packageInfo.description) {
if (releaseNotes) {
const notes = node("section", "package-description");
const content = node("div", "package-description-content");
content.append(renderMarkdown(packageInfo.description));
content.append(renderMarkdown(releaseNotes));
notes.append(
node("strong", "package-description-title", "版本更新内容"),
content,
......@@ -116,6 +121,7 @@ function renderLibrary(applications) {
}, application.display_name, {
compact: true,
applicationDescription: application.description,
releaseNotes: application.release_description,
});
const detail = node("a", "history-link", "查看已发布版本");
detail.href = `/download/${encodeURIComponent(application.app_id)}`;
......@@ -129,11 +135,16 @@ function renderApplication(application) {
document.querySelector("#page-description").textContent =
`${application.packages.length} 个已发布程序包 · App ID:${application.app_id}`;
const applicationDescription = document.querySelector("#application-description");
applicationDescription.textContent = application.description || "";
document.querySelector("#application-description-content").textContent =
application.description || "";
applicationDescription.hidden = !application.description;
packageList.replaceChildren();
for (const packageInfo of application.packages) {
packageList.append(packageCard(packageInfo, application.display_name));
packageList.append(
packageCard(packageInfo, application.display_name, {
releaseNotes: packageInfo.description,
}),
);
}
}
......
......@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>程序下载中心</title>
<link rel="stylesheet" href="/assets/downloads.css?v=20261028-1">
<script type="module" src="/assets/downloads.js?v=20261028-1"></script>
<link rel="stylesheet" href="/assets/downloads.css?v=20261029-2">
<script type="module" src="/assets/downloads.js?v=20261029-2"></script>
</head>
<body>
<main class="page">
......@@ -21,7 +21,10 @@
<div class="eyebrow">DOWNLOAD LIBRARY</div>
<h1 id="page-title">获取程序与更新包</h1>
<p id="page-description">浏览已发布的程序版本,下载前请核对平台与架构。</p>
<p id="application-description" class="application-description" hidden></p>
<section id="application-description" class="application-description" hidden>
<strong class="application-description-title">程序介绍</strong>
<p id="application-description-content"></p>
</section>
</section>
<div id="error" class="error-card" role="status" hidden></div>
<section id="package-list" class="package-list" aria-live="polite"></section>
......
......@@ -142,6 +142,8 @@ class DistributionTests(unittest.TestCase):
public_library = self.client.get("/api/public/apps")
self.assertEqual(public_application.json()["description"], description)
self.assertEqual(public_library.json()[0]["description"], description)
self.assertEqual(public_library.json()[0]["release_description"], "Release notes.")
self.assertEqual(public_application.json()["packages"][0]["description"], "Release notes.")
def test_generic_package_is_recorded_and_published_for_public_download(self) -> None:
self.create_application()
......@@ -388,6 +390,7 @@ class DistributionTests(unittest.TestCase):
self.assertIn(".shell", stylesheet.text)
self.assertEqual(download_script.status_code, 200)
self.assertIn("renderLibrary", download_script.text)
self.assertIn('"程序介绍"', download_script.text)
self.assertEqual(download_stylesheet.status_code, 200)
self.assertIn(".package-card", download_stylesheet.text)
self.assertEqual(markdown_script.status_code, 200)
......@@ -402,8 +405,8 @@ class DistributionTests(unittest.TestCase):
"/download/my-program",
"/assets/app.js?v=20261028-1",
"/assets/app.css?v=20261028-1",
"/assets/downloads.js?v=20261028-1",
"/assets/downloads.css?v=20261028-1",
"/assets/downloads.js?v=20261029-2",
"/assets/downloads.css?v=20261029-2",
):
with self.subTest(path=path):
response = self.client.get(path)
......@@ -418,10 +421,12 @@ class DistributionTests(unittest.TestCase):
dashboard = self.client.get("/")
downloads = self.client.get("/downloads")
package_page = self.client.get("/download/my-program")
for asset in ("app.js", "app.css"):
self.assertIn(f"/assets/{asset}?v=20261028-1", dashboard.text)
for asset in ("downloads.js", "downloads.css"):
self.assertIn(f"/assets/{asset}?v=20261028-1", downloads.text)
self.assertIn(f"/assets/{asset}?v=20261029-2", downloads.text)
self.assertIn("程序介绍", package_page.text)
def test_public_download_surfaces_are_remote_but_management_is_not(self) -> None:
self.create_application()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment