mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
* fix(og): render org profile images in publisher OG cards Pass publisher avatar, kind, and installs into OG meta URLs and allow safely fetching public HTTPS org logos when generating profile images. * fix: render org profile images in publisher OG cards Org logos use public HTTPS URLs outside the GitHub/gravatar allowlist, so OG generation fell back to the default mark. Allow SSRF-safe public fetches for publisher profile avatars and embed avatar/kind metadata in OG URLs. * fix(og): show Downloads instead of Installs on OG cards Switch skill, plugin, and publisher OG image generators to read and label download counts, with legacy installs query param fallback. * fix(og): type skill API payload for canonical stat reads Export SkillStatReadable so fetchSkillOgMeta can pass API skill stats through readCanonicalStat without a TypeScript error. * fix(og): format compact downloads in OG cards Query-param download counts were rendered as raw integers on publisher OG images. Reuse formatCompactStat, add download icon + lowercase label, bump layout versions, and refresh org profile visual proof. * fix(og): use Downloads label without icon on OG cards Remove the download SVG from OG stat blocks and show only a muted "Downloads" label above compact values. Bump skill/plugin/publisher layout versions to bust cached social previews.
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { formatCompactStat } from "../../src/lib/numberFormat";
|
|
|
|
function cleanQueryString(value: unknown) {
|
|
if (typeof value !== "string") return "";
|
|
return value.trim();
|
|
}
|
|
|
|
export function readOgDownloadsQuery(query: { downloads?: unknown; installs?: unknown }) {
|
|
const downloads = cleanQueryString(query.downloads);
|
|
if (downloads) return downloads;
|
|
return cleanQueryString(query.installs);
|
|
}
|
|
|
|
export function formatOgStat(value: number | null | undefined) {
|
|
const numericValue = typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
return formatCompactStat(numericValue);
|
|
}
|
|
|
|
function parseRawOgStatValue(raw: string) {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) return null;
|
|
if (/^-?\d+$/.test(trimmed)) {
|
|
const parsed = Number.parseInt(trimmed, 10);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
const compact = /^(-?\d+(?:\.\d+)?)([kKmM])$/.exec(trimmed);
|
|
if (!compact) return null;
|
|
const base = Number.parseFloat(compact[1]);
|
|
if (!Number.isFinite(base)) return null;
|
|
return compact[2].toLowerCase() === "k" ? Math.round(base * 1_000) : Math.round(base * 1_000_000);
|
|
}
|
|
|
|
export function resolveOgDownloadsDisplay(
|
|
query: { downloads?: unknown; installs?: unknown },
|
|
fallback?: number | null,
|
|
) {
|
|
const raw = readOgDownloadsQuery(query);
|
|
if (raw) {
|
|
const numeric = parseRawOgStatValue(raw);
|
|
if (numeric !== null) return formatOgStat(numeric);
|
|
return raw;
|
|
}
|
|
return formatOgStat(fallback);
|
|
}
|