feat: add Cloudflare Pages HTML frontend#4
Conversation
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> Agent-Logs-Url: https://github.com/XMOJ-Script-dev/ELXMOJ/sessions/64320354-8da8-471e-b22d-04461a839d1e
There was a problem hiding this comment.
Pull request overview
Adds a static, no-build landing page under docs/ intended for deployment to Cloudflare Pages, including runtime GitHub Releases fetching and baseline security headers.
Changes:
- Added a self-contained
docs/index.htmllanding page (hero/features/download/how/footer) with runtime download rendering via GitHub Releases API. - Added Cloudflare Pages
_headersto apply basic security headers site-wide.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| docs/index.html | New static landing page with styling + JS that fetches and renders latest release download assets. |
| docs/_headers | Cloudflare Pages security headers applied to all routes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const links = assets.map(a => ` | ||
| <a class="dl-link" href="${a.browser_download_url}" target="_blank" rel="noopener"> | ||
| <span class="dl-name">${formatLabel(a.name)}</span> | ||
| <span class="dl-size">${formatBytes(a.size)}</span> |
There was a problem hiding this comment.
The download cards are constructed with template literals and injected via innerHTML, interpolating asset.name / browser_download_url from the GitHub API. This makes the page vulnerable to DOM XSS if any of those fields contain unexpected HTML (even if unlikely). Prefer building DOM nodes and setting textContent/href attributes, or at minimum HTML-escape interpolated text before assigning to innerHTML.
| const date = isNaN(rawDate.getTime()) | ||
| ? release.published_at || "" | ||
| : rawDate.toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" }); | ||
| versionTag.innerHTML = `最新版本:<a href="${release.html_url}" target="_blank" rel="noopener">${release.tag_name}</a> · 发布于 ${date}`; |
There was a problem hiding this comment.
versionTag.innerHTML = ... interpolates release.tag_name and release.html_url from the GitHub API into HTML. To avoid potential DOM XSS and malformed markup, set textContent for the tag name/date and set the anchor href via DOM APIs instead of using innerHTML.
| versionTag.innerHTML = `最新版本:<a href="${release.html_url}" target="_blank" rel="noopener">${release.tag_name}</a> · 发布于 ${date}`; | |
| versionTag.textContent = "最新版本:"; | |
| const versionLink = document.createElement("a"); | |
| versionLink.href = release.html_url || RELEASES_PAGE; | |
| versionLink.target = "_blank"; | |
| versionLink.rel = "noopener"; | |
| versionLink.textContent = release.tag_name || ""; | |
| versionTag.appendChild(versionLink); | |
| if (date) { | |
| versionTag.appendChild(document.createTextNode(" · 发布于 " + date)); | |
| } |
| const rawDate = new Date(release.published_at); | ||
| const date = isNaN(rawDate.getTime()) | ||
| ? release.published_at || "" | ||
| : rawDate.toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" }); |
There was a problem hiding this comment.
new Date(release.published_at) will produce a valid date (epoch 1970-01-01) when published_at is null/missing (e.g., certain API edge cases), so the isNaN check won't catch it and the UI would show an incorrect publish date. Consider explicitly checking release.published_at truthiness before formatting, and fall back to an empty string or "未知" when absent.
| const rawDate = new Date(release.published_at); | |
| const date = isNaN(rawDate.getTime()) | |
| ? release.published_at || "" | |
| : rawDate.toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" }); | |
| let date = "未知"; | |
| if (release.published_at) { | |
| const rawDate = new Date(release.published_at); | |
| date = isNaN(rawDate.getTime()) | |
| ? release.published_at | |
| : rawDate.toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" }); | |
| } |
| /* ── RESPONSIVE ──────────────────────────────── */ | ||
| @media (max-width: 600px) { | ||
| nav .nav-links { | ||
| display: none; |
There was a problem hiding this comment.
On small screens (max-width: 600px) the primary nav links are set to display: none, which removes in-page navigation for mobile users (and for screen readers at that breakpoint). Consider replacing this with a collapsible menu or a horizontally scrollable nav instead of hiding the links entirely.
| display: none; | |
| overflow-x: auto; | |
| white-space: nowrap; |
| <meta property="og:title" content="ELXMOJ — XMOJ 桌面端" /> | ||
| <meta property="og:description" content="ELXMOJ 是一款基于 Electron 的 XMOJ 桌面客户端,自动注入 XMOJ-Script,支持脚本自动更新、设置持久化。" /> | ||
| <meta property="og:type" content="website" /> | ||
| <link rel="icon" type="image/x-icon" href="favicon.ico" /> |
There was a problem hiding this comment.
<link rel="icon" ... href="favicon.ico" /> will 404 when deploying with Cloudflare Pages output directory set to docs because docs/ currently doesn't include favicon.ico (it's at repo root). Either copy the icon into docs/ or change the href to a path that will exist in the deployed site.
| <link rel="icon" type="image/x-icon" href="favicon.ico" /> | |
| <link rel="icon" type="image/x-icon" href="/favicon.ico" /> |
| if (n.includes("win")) return { os: "win", label: "Windows" }; | ||
| if (n.includes("mac") || n.includes("darwin")) return { os: "mac", label: "macOS" }; |
There was a problem hiding this comment.
classifyAsset() checks n.includes("win") before checking for "darwin". Asset names containing "darwin" (common for mac builds) will match the "win" branch and be misclassified as Windows. Reorder the checks (mac/darwin before win) or use stricter matching to avoid substring collisions.
| if (n.includes("win")) return { os: "win", label: "Windows" }; | |
| if (n.includes("mac") || n.includes("darwin")) return { os: "mac", label: "macOS" }; | |
| if (n.includes("mac") || n.includes("darwin")) return { os: "mac", label: "macOS" }; | |
| if (n.includes("win")) return { os: "win", label: "Windows" }; |
Static landing page for ELXMOJ deployable to Cloudflare Pages with no build step required.
docs/index.htmlSingle self-contained file — no dependencies, no build tooling.
releases/latestfrom GitHub API at runtime; renders per-OS cards (Windows/macOS/Linux) with filename + size; falls back to a GitHub Releases link on API failureDesign matches the existing green palette (
#0a7a4f) fromsrc/settings.html.docs/_headersCloudflare Pages security headers applied to
/*:Deploy
In the Cloudflare Pages dashboard set Build output directory to
docs; leave build command empty.📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.