From 09c89fa13b9327e8c75066a67f4d150ae3a06cfb Mon Sep 17 00:00:00 2001
From: cooronx <2197083441@qq.com>
Date: Mon, 13 Apr 2026 21:19:29 +0800
Subject: [PATCH 1/5] feat: replace footer star section fetch with generated
JSON
---
.github/workflows/master.yml | 3 +
.github/workflows/pr_ci.yml | 3 +
docusaurus.config.js | 52 +----
package.json | 1 +
scripts/generate-footer-stars.js | 90 +++++++++
src/components/FooterMoreBadges/badges.json | 58 ++++++
src/components/FooterMoreBadges/index.js | 180 ++++++++++++++++++
src/components/FooterMoreBadges/stars.json | 13 ++
.../FooterMoreBadges/styles.module.css | 47 +++++
src/css/custom.css | 40 ----
src/theme/Footer/Links/MultiColumn/index.js | 75 ++++++++
11 files changed, 471 insertions(+), 91 deletions(-)
create mode 100644 scripts/generate-footer-stars.js
create mode 100644 src/components/FooterMoreBadges/badges.json
create mode 100644 src/components/FooterMoreBadges/index.js
create mode 100644 src/components/FooterMoreBadges/stars.json
create mode 100644 src/components/FooterMoreBadges/styles.module.css
create mode 100644 src/theme/Footer/Links/MultiColumn/index.js
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index c768916f9..6c0091a4a 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -48,6 +48,9 @@ jobs:
if: github.event_name != 'pull_request'
run: yarn crowdin:sync
+ - name: Generate footer stars
+ run: yarn generate:footer-stars
+
- name: Build
run: yarn build
diff --git a/.github/workflows/pr_ci.yml b/.github/workflows/pr_ci.yml
index 8c68a5795..3024e6cec 100644
--- a/.github/workflows/pr_ci.yml
+++ b/.github/workflows/pr_ci.yml
@@ -22,5 +22,8 @@ jobs:
- name: Install dependencies
run: yarn install
+ - name: Generate footer stars
+ run: yarn generate:footer-stars
+
- name: Build website
run: yarn build --locale en
diff --git a/docusaurus.config.js b/docusaurus.config.js
index fb91717f2..ab6625af1 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -234,60 +234,10 @@ module.exports = {
},
{
title: "More",
+ className: "footer-more-column",
items: [
{
html: `
-
-
-
-
-
-
-
- `,
- },
- {
- html: `
-
-
-
-
-
-
-
- `,
- },
- {
- html: `
-
-
-
-
-
-
-
- `,
- },
- {
- html: `
-
-
-
-
-
-
-
- `,
- },
- {
- html: `
-
-
-
- `,
- },
- {
- html: `
-`,
- },
- ],
+ items: [],
},
],
logo: {
From 438b7c5e204e3d407b14235711c0ce33d9c22c13 Mon Sep 17 00:00:00 2001
From: cooronx <2197083441@qq.com>
Date: Wed, 15 Apr 2026 22:53:23 +0800
Subject: [PATCH 3/5] feat: replace index keys in footer links
---
src/theme/Footer/Links/MultiColumn/index.js | 30 ++++++++++++++++-----
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/src/theme/Footer/Links/MultiColumn/index.js b/src/theme/Footer/Links/MultiColumn/index.js
index 50a88aa19..9431008df 100644
--- a/src/theme/Footer/Links/MultiColumn/index.js
+++ b/src/theme/Footer/Links/MultiColumn/index.js
@@ -4,6 +4,24 @@ import {ThemeClassNames} from "@docusaurus/theme-common";
import LinkItem from "@theme/Footer/LinkItem";
import FooterMoreBadges from "@site/src/components/FooterMoreBadges";
+function getItemKey(item) {
+ return [
+ item.href,
+ item.to,
+ item.label,
+ item.className,
+ item.html,
+ ].filter(Boolean).join("::");
+}
+
+function getColumnKey(column) {
+ return [
+ column.title,
+ column.className,
+ ...column.items.map(getItemKey),
+ ].filter(Boolean).join("::");
+}
+
function ColumnLinkItem({item}) {
return item.html ? (
- {column.items.map((item, index) => (
-
+ {column.items.map((item) => (
+
))}
>
) : (
- {column.items.map((item, index) => (
-
+ {column.items.map((item) => (
+
))}
)}
@@ -67,8 +85,8 @@ function Column({column}) {
export default function FooterLinksMultiColumn({columns}) {
return (
- {columns.map((column, index) => (
-
+ {columns.map((column) => (
+
))}
);
From f621ea211457ed954e68ee0cd3d379ce797f7e7e Mon Sep 17 00:00:00 2001
From: cooronx <54298598+cooronx@users.noreply.github.com>
Date: Thu, 16 Apr 2026 00:17:44 +0800
Subject: [PATCH 4/5] feat: use smaller stable keys for footer items
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
src/theme/Footer/Links/MultiColumn/index.js | 27 +++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/theme/Footer/Links/MultiColumn/index.js b/src/theme/Footer/Links/MultiColumn/index.js
index 9431008df..a935742f4 100644
--- a/src/theme/Footer/Links/MultiColumn/index.js
+++ b/src/theme/Footer/Links/MultiColumn/index.js
@@ -4,14 +4,37 @@ import {ThemeClassNames} from "@docusaurus/theme-common";
import LinkItem from "@theme/Footer/LinkItem";
import FooterMoreBadges from "@site/src/components/FooterMoreBadges";
+function getShortHash(value) {
+ let hash = 0;
+
+ for (let index = 0; index < value.length; index += 1) {
+ hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
+ }
+
+ return hash.toString(36);
+}
+
function getItemKey(item) {
- return [
+ if (item.id) {
+ return item.id;
+ }
+
+ const stableKey = [
item.href,
item.to,
item.label,
item.className,
- item.html,
].filter(Boolean).join("::");
+
+ if (stableKey) {
+ return stableKey;
+ }
+
+ if (item.html) {
+ return `html:${getShortHash(item.html)}`;
+ }
+
+ return "item";
}
function getColumnKey(column) {
From c7a3592357cde49d1273dd0313b27c8013ae012f Mon Sep 17 00:00:00 2001
From: cooronx <2197083441@qq.com>
Date: Thu, 16 Apr 2026 01:01:25 +0800
Subject: [PATCH 5/5] feat: xtract footer badges into svg icon components
---
src/components/FooterMoreBadges/badges.json | 24 +--
src/components/FooterMoreBadges/index.js | 150 ++++--------------
.../FooterMoreBadges/styles.module.css | 76 ++++++++-
static/img/footer/github.svg | 3 +
static/img/footer/x.svg | 3 +
5 files changed, 116 insertions(+), 140 deletions(-)
create mode 100644 static/img/footer/github.svg
create mode 100644 static/img/footer/x.svg
diff --git a/src/components/FooterMoreBadges/badges.json b/src/components/FooterMoreBadges/badges.json
index 092941c0a..d26952cd5 100644
--- a/src/components/FooterMoreBadges/badges.json
+++ b/src/components/FooterMoreBadges/badges.json
@@ -3,56 +3,48 @@
"label": "Casbin",
"owner": "casbin",
"repo": "casbin",
- "href": "https://github.com/casbin/casbin",
- "labelWidth": 64
+ "href": "https://github.com/casbin/casbin"
},
{
"label": "jCasbin",
"owner": "casbin",
"repo": "jcasbin",
- "href": "https://github.com/casbin/jcasbin",
- "labelWidth": 70
+ "href": "https://github.com/casbin/jcasbin"
},
{
"label": "Node-Casbin",
"owner": "casbin",
"repo": "node-casbin",
- "href": "https://github.com/casbin/node-casbin",
- "labelWidth": 96
+ "href": "https://github.com/casbin/node-casbin"
},
{
"label": "PHP-Casbin",
"owner": "php-casbin",
"repo": "php-casbin",
- "href": "https://github.com/php-casbin/php-casbin",
- "labelWidth": 90
+ "href": "https://github.com/php-casbin/php-casbin"
},
{
"label": "PyCasbin",
"owner": "casbin",
"repo": "pycasbin",
- "href": "https://github.com/casbin/pycasbin",
- "labelWidth": 76
+ "href": "https://github.com/casbin/pycasbin"
},
{
"label": "Casbin.NET",
"owner": "casbin",
"repo": "Casbin.NET",
- "href": "https://github.com/casbin/Casbin.NET",
- "labelWidth": 88
+ "href": "https://github.com/casbin/Casbin.NET"
},
{
"label": "Casbin-CPP",
"owner": "casbin",
"repo": "casbin-cpp",
- "href": "https://github.com/casbin/casbin-cpp",
- "labelWidth": 90
+ "href": "https://github.com/casbin/casbin-cpp"
},
{
"label": "Casbin-RS",
"owner": "casbin",
"repo": "casbin-rs",
- "href": "https://github.com/casbin/casbin-rs",
- "labelWidth": 84
+ "href": "https://github.com/casbin/casbin-rs"
}
]
diff --git a/src/components/FooterMoreBadges/index.js b/src/components/FooterMoreBadges/index.js
index 21dfe0f6f..412108190 100644
--- a/src/components/FooterMoreBadges/index.js
+++ b/src/components/FooterMoreBadges/index.js
@@ -1,24 +1,16 @@
-import React, {useId} from "react";
+import React from "react";
import styles from "./styles.module.css";
import githubBadges from "./badges.json";
import starsData from "./stars.json";
+const GITHUB_BADGES = githubBadges;
+const STAR_COUNTS = starsData.stars ?? {};
const STAR_FORMATTER = new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 1,
});
-const GITHUB_BADGES = githubBadges;
-const STAR_COUNTS = starsData.stars ?? {};
-
-// github SVG path
-const GITHUB_ICON_PATH =
- "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12";
-// X SVG path
-const X_ICON_PATH =
- "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.186z";
-
function getRepoKey(owner, repo) {
return `${owner}/${repo}`;
}
@@ -27,113 +19,6 @@ function formatStars(count) {
return STAR_FORMATTER.format(count).replace(/\.0(?=[A-Za-z])/u, "").toLowerCase();
}
-function getCountWidth(value) {
- return Math.max(27, 12 + value.length * 4);
-}
-
-function GitHubStarBadge({label, labelWidth, stars}) {
- const badgeId = useId().replace(/:/gu, "");
- const gradientId = `footer-badge-gradient-${badgeId}`;
- const safeStars = Number.isFinite(stars) ? stars : 0;
- const formattedStars = formatStars(safeStars);
- const countWidth = getCountWidth(formattedStars);
- const badgeWidth = labelWidth + countWidth + 7;
- const labelCenterX = labelWidth / 2 + 8.5;
- const countCenterX = labelWidth + 6 + countWidth / 2;
-
- return (
-
- );
-}
-
-function XFollowBadge() {
- const badgeId = useId().replace(/:/gu, "");
- const gradientId = `footer-badge-x-gradient-${badgeId}`;
-
- return (
-
- );
-}
-
function BadgeLink({href, ariaLabel, children}) {
return (
+
+
+ {label}
+
+
+ {formattedStars}
+
+ );
+}
+
+function XFollowBadge() {
+ return (
+
+
+ Follow @casbinHQ
+
+ );
+}
+
function GitHubBadgeLink({badge, stars}) {
- const {label, href, labelWidth} = badge;
+ const {label, href} = badge;
return (
-
+
);
}
diff --git a/src/components/FooterMoreBadges/styles.module.css b/src/components/FooterMoreBadges/styles.module.css
index 079106642..c8e3b0cd7 100644
--- a/src/components/FooterMoreBadges/styles.module.css
+++ b/src/components/FooterMoreBadges/styles.module.css
@@ -11,17 +11,85 @@
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
-.badgeSvg {
- display: block;
+.badgeSurface {
+ display: inline-flex;
+ align-items: stretch;
+ width: 135px;
+ height: 20px;
+ overflow: hidden;
+ border: 1px solid #d5d5d5;
+ border-radius: 2px;
+ background: #fcfcfc;
+ color: #333;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 11px;
+ font-weight: 700;
+ line-height: 1;
transition: filter 0.3s cubic-bezier(0.4, 0, 0.2, 1);
will-change: filter;
}
+.badgeLabelSection {
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 6px;
+ width: 96px;
+ padding: 0 6px;
+ box-sizing: border-box;
+}
+
+.badgeDivider {
+ position: relative;
+ width: 7px;
+ background: #fafafa;
+ border-left: 1px solid #d5d5d5;
+}
+
+.badgeDivider::before {
+ content: "";
+ position: absolute;
+ left: -1px;
+ top: 6px;
+ border-top: 4px solid transparent;
+ border-bottom: 4px solid transparent;
+ border-left: 4px solid #fafafa;
+}
+
+.badgeCountSection {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ background: #fafafa;
+}
+
+.badgeIcon {
+ width: 14px;
+ height: 14px;
+ flex-shrink: 0;
+}
+
+.badgeText {
+ display: inline-block;
+ min-width: 0;
+ white-space: nowrap;
+ text-align: left;
+}
+
+.followBadge {
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 0 10px;
+ box-sizing: border-box;
+}
+
.badgeLink:hover {
transform: translateY(-3px) scale(1.08) rotate(2deg);
}
-.badgeLink:hover .badgeSvg {
+.badgeLink:hover .badgeSurface {
filter: drop-shadow(0 6px 12px rgb(59 130 246 / 35%)) brightness(1.15);
}
@@ -30,7 +98,7 @@
transition-duration: 0.1s;
}
-.badgeLink:active .badgeSvg {
+.badgeLink:active .badgeSurface {
filter: drop-shadow(0 2px 4px rgb(59 130 246 / 25%)) brightness(1.1);
}
diff --git a/static/img/footer/github.svg b/static/img/footer/github.svg
new file mode 100644
index 000000000..43b3ec5fe
--- /dev/null
+++ b/static/img/footer/github.svg
@@ -0,0 +1,3 @@
+
diff --git a/static/img/footer/x.svg b/static/img/footer/x.svg
new file mode 100644
index 000000000..bea4b6f7e
--- /dev/null
+++ b/static/img/footer/x.svg
@@ -0,0 +1,3 @@
+