Site: show downloads across every release [skip ci]

/release.json adds the sum of every release asset's downloads; the
update manifests are the apps' checks, not downloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-13 13:43:38 -04:00
1 parent 87c42fde0e
commit 854c360ea1
7 files changed
+81 -26

No files matched your search

+10 -3
View File
@@ -7,7 +7,7 @@ import { night } from "./night.ts";
import { rhythm } from "./rhythm.ts";
import { playground } from "./play.ts";
import { film } from "./film.ts";
import { said, speak, type Lang } from "./text.ts";
import { said, speak, word, type Lang } from "./text.ts";
import { RELEASES, latest } from "./release.ts";
const REPO = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice";
@@ -36,10 +36,11 @@ const footer = document.createElement("footer");
footer.id = "download";
const version = document.createElement("h2");
version.textContent = "Ely Office";
void latest().then((v) => (version.textContent = `Ely Office ${v}`));
const count = said("p", "count");
count.hidden = true;
const get = said("a", "get");
get.href = RELEASES;
footer.append(version, said("p", "platforms"), said("p", "needs"), said("p", "unsigned"), get);
footer.append(version, count, said("p", "platforms"), said("p", "needs"), said("p", "unsigned"), get);
const toggle = said("button", "other");
toggle.className = "lang";
@@ -57,3 +58,9 @@ main.append(section, playground(), shelf, film());
app.append(night(), toggle, main, footer);
say(lang);
reveal([shelf]);
void latest().then(({ version: v, downloads }) => {
version.textContent = `Ely Office ${v}`;
count.dataset.n = String(downloads);
count.textContent = word(lang, "count", downloads);
count.hidden = false;
});
+5 -4
View File
@@ -1,10 +1,11 @@
// Gitea redirects this to the newest release.
export const RELEASES = "https://git.zacharyzhang.com/ZacharyZhang-NY/ElyOffice/releases/latest";
/** The newest release's version, from the worker. */
export async function latest(): Promise<string> {
export type Release = { version: string; downloads: number };
/** The newest release and all downloads, from the worker. */
export async function latest(): Promise<Release> {
const res = await fetch("/release.json");
if (!res.ok) throw new Error(`release.json: ${res.status} ${await res.text()}`);
const { version } = (await res.json()) as { version: string };
return version;
return (await res.json()) as Release;
}
+10 -1
View File
@@ -17,6 +17,7 @@ const en = {
needs: "macOS 14 or later, Apple silicon.",
unsigned: "Not notarized. If macOS blocks the app, open System Settings › Privacy & Security and click Open Anyway.",
get: "Download",
count: "{n} downloads so far.",
other: "中文",
roads: "Drag a file down the road.",
fileDoc: "ElyDoc file",
@@ -53,6 +54,7 @@ const zh: typeof en = {
needs: "macOS 14 或更新,Apple 芯片。",
unsigned: "未经公证。macOS 拦下应用时,打开“系统设置 › 隐私与安全性”,点“仍要打开”。",
get: "前往下载",
count: "已下载 {n} 次。",
other: "English",
roads: "把文件沿着路拖过去。",
fileDoc: "ElyDoc 文件",
@@ -83,10 +85,17 @@ export function said<K extends keyof HTMLElementTagNameMap>(tag: K, key: Key): H
return el;
}
/** `key` in `lang`, with `n` written for {n}. */
export function word(lang: Lang, key: Key, n?: number): string {
const text = TEXT[lang][key];
return n === undefined ? text : text.replace("{n}", n.toLocaleString(lang === "zh" ? "zh-CN" : "en"));
}
/** Writes every keyed text in `lang`. */
export function speak(lang: Lang): void {
document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
for (const el of document.querySelectorAll<HTMLElement>("[data-t]")) {
el.textContent = TEXT[lang][el.dataset.t as Key];
const { n } = el.dataset;
el.textContent = word(lang, el.dataset.t as Key, n === undefined ? undefined : Number(n));
}
}