From 8d045ffd8b99f3d68baabe2e567ce6fcc5e08822 Mon Sep 17 00:00:00 2001 From: gitlawr Date: Tue, 9 Jun 2026 17:57:46 +0800 Subject: [PATCH] feat(header): add GitHub star button to top bar Compact pill (GitHub icon | star count) placed left of the version display. Star count is fetched from the GitHub API once per 24h and cached in localStorage; the button hides entirely when no count is available (e.g. air-gapped deployments), matching the existing update- check graceful-degradation pattern. Fetch is bounded by a 4s abort timeout to avoid hanging requests offline. --- src/layouts/extraRender.tsx | 2 + src/layouts/github-star.tsx | 140 ++++++++++++++++++++++++++++++++++++ src/locales/en-US/common.ts | 1 + src/locales/ja-JP/common.ts | 1 + src/locales/ru-RU/common.ts | 1 + src/locales/tr-TR/common.ts | 1 + src/locales/zh-CN/common.ts | 1 + 7 files changed, 147 insertions(+) create mode 100644 src/layouts/github-star.tsx diff --git a/src/layouts/extraRender.tsx b/src/layouts/extraRender.tsx index 69fd4c98..a240a996 100644 --- a/src/layouts/extraRender.tsx +++ b/src/layouts/extraRender.tsx @@ -18,6 +18,7 @@ import { useAtom } from 'jotai'; import { useMemo } from 'react'; import styled from 'styled-components'; import { DEFAULT_ENTER_PAGE } from '../config/settings'; +import GithubStar from './github-star'; const NewLabel = styled.span` position: relative; @@ -260,6 +261,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => { {contextHolder} + {process.env.ENABLE_ENTERPRISE !== 'true' && }
{ + if (n >= 1000) { + const k = n / 1000; + return k >= 10 ? `${Math.round(k)}k` : `${k.toFixed(1)}k`; + } + return String(n); +}; + +type CacheEntry = { value: number; time: number }; + +const readCache = (): CacheEntry | null => { + try { + const raw = localStorage.getItem(CACHE_KEY); + if (!raw) return null; + const parsed = JSON.parse(raw); + if (typeof parsed?.value !== 'number' || typeof parsed?.time !== 'number') { + return null; + } + return parsed; + } catch { + return null; + } +}; + +const writeCache = (value: number) => { + try { + localStorage.setItem( + CACHE_KEY, + JSON.stringify({ value, time: Date.now() }) + ); + } catch { + // ignore quota errors + } +}; + +const GithubStar = () => { + const intl = useIntl(); + const [count, setCount] = useState( + () => readCache()?.value ?? null + ); + + useEffect(() => { + const cached = readCache(); + const fresh = cached && Date.now() - cached.time < CACHE_TTL; + if (fresh) return; + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + fetch(`https://api.github.com/repos/${REPO}`, { signal: controller.signal }) + .then((r) => (r.ok ? r.json() : null)) + .then((data) => { + if (!data || typeof data.stargazers_count !== 'number') return; + setCount(data.stargazers_count); + writeCache(data.stargazers_count); + }) + .catch(() => { + // offline, blocked, rate-limited — stay hidden if no cache + }) + .finally(() => clearTimeout(timer)); + + return () => { + clearTimeout(timer); + controller.abort(); + }; + }, []); + + return ( + + + + + + + + {count != null ? formatCount(count) : 'Star'} + + + + + ); +}; + +export default GithubStar; diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 3bb94a99..f805a0d1 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -169,6 +169,7 @@ export default { 'common.time.hour': 'hour', 'common.time.minute': 'minutes', 'common.issue.report': 'Report an issue', + 'common.github.star.tooltip': 'Star us on GitHub', 'common.social.discord': 'Join Our Discord', 'common.table.mark': 'Comment', 'common.table.rollback.mark': 'Rollback Comment', diff --git a/src/locales/ja-JP/common.ts b/src/locales/ja-JP/common.ts index 08faa04e..aab4a336 100644 --- a/src/locales/ja-JP/common.ts +++ b/src/locales/ja-JP/common.ts @@ -170,6 +170,7 @@ export default { 'common.time.hour': '時間', 'common.time.minute': '分', 'common.issue.report': '問題を報告', + 'common.github.star.tooltip': 'GitHub でスターをつける', 'common.social.discord': 'Discordに参加', 'common.table.mark': 'コメント', 'common.table.rollback.mark': 'ロールバックコメント', diff --git a/src/locales/ru-RU/common.ts b/src/locales/ru-RU/common.ts index 709ed946..f740f651 100644 --- a/src/locales/ru-RU/common.ts +++ b/src/locales/ru-RU/common.ts @@ -167,6 +167,7 @@ export default { 'common.time.hour': 'Час', 'common.time.minute': 'Минута', 'common.issue.report': 'Сообщить о проблеме', + 'common.github.star.tooltip': 'Поставьте нам звезду на GitHub', 'common.social.discord': 'Присоединиться к Discord', 'common.table.mark': 'Комментарий', 'common.table.rollback.mark': 'Комментарий к откату', diff --git a/src/locales/tr-TR/common.ts b/src/locales/tr-TR/common.ts index d587f51c..660978b4 100644 --- a/src/locales/tr-TR/common.ts +++ b/src/locales/tr-TR/common.ts @@ -167,6 +167,7 @@ export default { 'common.time.hour': 'saat', 'common.time.minute': 'dakika', 'common.issue.report': 'Sorun bildir', + 'common.github.star.tooltip': "GitHub'da bize yıldız verin", 'common.social.discord': "Discord'umuza Katılın", 'common.table.mark': 'Yorum', 'common.table.rollback.mark': 'Geri Alma Yorumu', diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index ae0e48aa..1a21278a 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -161,6 +161,7 @@ export default { 'common.time.hour': '小时', 'common.time.minute': '分钟', 'common.issue.report': '问题反馈', + 'common.github.star.tooltip': '在 GitHub 上 Star 我们', 'common.social.discord': '加入我们的Discord', 'common.table.mark': '备注', 'common.table.rollback.mark': '回滚备注',