Skip to content

Commit c06d8fc

Browse files
committed
feat(toc): add configurable badge style (number/katakana/roman)
Replace the boolean `useJapaneseBadge` with a new `badgeStyle` config option that supports three TOC badge styles: - "number": plain numeric badges (1, 2, 3...) - "katakana": Japanese katakana badges (original style) - "roman": Roman numeral badges (new) The old `useJapaneseBadge` boolean is kept as deprecated for backward compatibility. All TOC components (Sidebar, Floating, Mobile, Card) are updated to use the new unified `getTOCBadge()` function.
1 parent aa63f9a commit c06d8fc

16 files changed

Lines changed: 123 additions & 88 deletions

File tree

src/components/features/toc/FloatingTOC.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ interface Props {
1414
1515
const { headings: _ = [] } = Astro.props;
1616
const tocDepth = siteConfig.toc.depth;
17-
const useJapaneseBadge = siteConfig.toc.useJapaneseBadge;
17+
const badgeStyle = siteConfig.toc.badgeStyle || (siteConfig.toc.useJapaneseBadge ? "katakana" : "number");
1818
---
1919

2020
<div
2121
class="floating-toc-wrapper"
2222
data-depth={tocDepth}
23-
data-japanese-badge={useJapaneseBadge}
23+
data-badge-style={badgeStyle}
2424
>
2525
<FloatingButton
2626
id="floating-toc-btn"

src/components/features/toc/SidebarTOC.astro

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const { class: className } = Astro.props;
1818
</table-of-contents>
1919

2020
<script>
21-
import { JAPANESE_KATAKANA } from "./utils/japanese-katakana";
21+
import { getTOCBadge } from "./utils/japanese-katakana";
2222
import {
2323
extractHeadings,
2424
generateTOCItems,
@@ -299,11 +299,7 @@ const { class: className } = Astro.props;
299299

300300
let badgeContent: string;
301301
if (item.depth === 0) {
302-
badgeContent =
303-
config.useJapaneseBadge &&
304-
h1Count - 1 < JAPANESE_KATAKANA.length
305-
? JAPANESE_KATAKANA[h1Count - 1]
306-
: h1Count.toString();
302+
badgeContent = getTOCBadge(h1Count - 1, config.badgeStyle);
307303
h1Count++;
308304
} else if (item.depth === 1) {
309305
badgeContent =

src/components/features/toc/hooks/useMobileTOC.ts

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* 处理移动端目录的状态管理和交互逻辑
44
*/
55

6+
import { getTOCBadge, type TOCBadgeStyle } from "../utils/japanese-katakana";
7+
68
export interface TOCItem {
79
id: string;
810
text: string;
@@ -18,37 +20,14 @@ export interface PostItem {
1820
}
1921

2022
export interface TOCConfig {
21-
useJapaneseBadge: boolean;
23+
badgeStyle: TOCBadgeStyle;
2224
depth: number;
2325
}
2426

2527
/**
2628
* 生成目录项
2729
*/
2830
export function generateTOCItems(config: TOCConfig): TOCItem[] {
29-
const japaneseHiragana = [
30-
"ア",
31-
"イ",
32-
"ウ",
33-
"エ",
34-
"オ",
35-
"カ",
36-
"キ",
37-
"ク",
38-
"ケ",
39-
"コ",
40-
"サ",
41-
"シ",
42-
"ス",
43-
"セ",
44-
"ソ",
45-
"タ",
46-
"チ",
47-
"ツ",
48-
"テ",
49-
"ト",
50-
];
51-
5231
const headings = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
5332
const items: TOCItem[] = [];
5433
let h1Count = 0;
@@ -70,15 +49,8 @@ export function generateTOCItems(config: TOCConfig): TOCItem[] {
7049

7150
// 只为 H1 标题生成 badge
7251
if (level === 1) {
52+
badge = getTOCBadge(h1Count, config.badgeStyle);
7353
h1Count++;
74-
if (
75-
config.useJapaneseBadge &&
76-
h1Count - 1 < japaneseHiragana.length
77-
) {
78-
badge = japaneseHiragana[h1Count - 1];
79-
} else {
80-
badge = h1Count.toString();
81-
}
8254
}
8355

8456
items.push({ id: heading.id, text, level, badge });
@@ -166,8 +138,11 @@ export function scrollToHeading(id: string, offset = 80): void {
166138
* 获取 TOC 配置
167139
*/
168140
export function getTOCConfig(): TOCConfig {
141+
const badgeStyle: TOCBadgeStyle =
142+
(window.siteConfig?.toc?.badgeStyle as TOCBadgeStyle) ??
143+
(window.siteConfig?.toc?.useJapaneseBadge ? "katakana" : "number");
169144
return {
170-
useJapaneseBadge: window.siteConfig?.toc?.useJapaneseBadge ?? false,
145+
badgeStyle,
171146
depth: window.siteConfig?.toc?.depth ?? 3,
172147
};
173148
}

src/components/features/toc/hooks/useTocNavigation.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,22 @@ export function createHeadingClickHandler(
7474
*/
7575
export function getTOCConfig(): {
7676
depth: number;
77+
badgeStyle: "number" | "katakana" | "roman";
7778
useJapaneseBadge: boolean;
7879
} {
7980
const siteConfig = (
8081
window as unknown as {
8182
siteConfig?: {
82-
toc?: { depth?: number; useJapaneseBadge?: boolean };
83+
toc?: { depth?: number; badgeStyle?: string; useJapaneseBadge?: boolean };
8384
};
8485
}
8586
).siteConfig;
87+
const badgeStyle = (siteConfig?.toc?.badgeStyle as "number" | "katakana" | "roman") ??
88+
(siteConfig?.toc?.useJapaneseBadge ? "katakana" : "number");
8689
return {
8790
depth: siteConfig?.toc?.depth ?? 3,
88-
useJapaneseBadge: siteConfig?.toc?.useJapaneseBadge ?? false,
91+
badgeStyle,
92+
useJapaneseBadge: badgeStyle === "katakana",
8993
};
9094
}
9195

src/components/features/toc/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ export {
7373
// Calculator utilities
7474
export {
7575
getKatakanaBadge,
76+
getTOCBadge,
7677
JAPANESE_KATAKANA,
7778
KATAKANA_COUNT,
79+
ROMAN_NUMERALS,
7880
} from "./utils/japanese-katakana";
81+
export type { TOCBadgeStyle } from "./utils/japanese-katakana";
7982
export {
8083
getMinLevel as calcMinLevel,
8184
generateTOCItems as calcTOCItems,

src/components/features/toc/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface TOCItem {
1414
level: number;
1515
/** 相对深度(0 = 顶级) */
1616
depth: number;
17-
/** 徽章文本(数字或日语字符) */
17+
/** 徽章文本(数字、日语片假名或罗马数字) */
1818
badge?: string;
1919
}
2020

@@ -28,7 +28,9 @@ export interface TOCConfig {
2828
mode: "float" | "sidebar";
2929
/** 标题深度(1-6) */
3030
depth: number;
31-
/** 是否使用日语徽章 */
31+
/** 徽章样式 */
32+
badgeStyle: "number" | "katakana" | "roman";
33+
/** @deprecated 使用 badgeStyle 代替 */
3234
useJapaneseBadge: boolean;
3335
}
3436

src/components/features/toc/types/toc.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface TOCItem {
1111
level: number;
1212
/** 相对深度(0 = 顶级) */
1313
depth: number;
14-
/** 徽章文本(数字或日语字符) */
14+
/** 徽章文本(数字、日语片假名或罗马数字) */
1515
badge?: string;
1616
}
1717

@@ -22,7 +22,9 @@ export interface TOCConfig {
2222
mode: "float" | "sidebar";
2323
/** 标题深度(1-6) */
2424
depth: number;
25-
/** 是否使用日语徽章 */
25+
/** 徽章样式 */
26+
badgeStyle: "number" | "katakana" | "roman";
27+
/** @deprecated 使用 badgeStyle 代替 */
2628
useJapaneseBadge: boolean;
2729
}
2830

src/components/features/toc/utils/japanese-katakana.ts

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
2-
* 日语片假名字符集
3-
* 用于 TOC 徽章显示
2+
* TOC 徽章字符集
3+
* 用于 TOC 徽章显示,支持多种计数方式
44
*/
55

66
/**
7-
* 完整的日语片假名字符集(46 个字符)
7+
* 日语片假名字符集(46 个字符)
88
* 按五十音图顺序排列
99
*/
1010
export const JAPANESE_KATAKANA = [
@@ -66,19 +66,77 @@ export const JAPANESE_KATAKANA = [
6666
"ン",
6767
] as const;
6868

69+
/**
70+
* 罗马数字字符集(30 个)
71+
* Ⅰ-Ⅻ 使用 Unicode 罗马数字字符,XIII-XXX 使用拉丁字母
72+
*/
73+
export const ROMAN_NUMERALS = [
74+
"Ⅰ",
75+
"Ⅱ",
76+
"Ⅲ",
77+
"Ⅳ",
78+
"Ⅴ",
79+
"Ⅵ",
80+
"Ⅶ",
81+
"Ⅷ",
82+
"Ⅸ",
83+
"Ⅹ",
84+
"Ⅺ",
85+
"Ⅻ",
86+
"XIII",
87+
"XIV",
88+
"XV",
89+
"XVI",
90+
"XVII",
91+
"XVIII",
92+
"XIX",
93+
"XX",
94+
"XXI",
95+
"XXII",
96+
"XXIII",
97+
"XXIV",
98+
"XXV",
99+
"XXVI",
100+
"XXVII",
101+
"XXVIII",
102+
"XXIX",
103+
"XXX",
104+
] as const;
105+
106+
export type TOCBadgeStyle = "number" | "katakana" | "roman";
107+
69108
export type JapaneseKatakanaChar = (typeof JAPANESE_KATAKANA)[number];
70109

71110
/**
72-
* 获取 TOC 徽章文本
111+
* 根据 badgeStyle 获取 TOC 徽章文本
73112
* @param index - 索引(从 0 开始)
74-
* @param useJapanese - 是否使用日语字符
113+
* @param badgeStyle - 徽章样式:"number" | "katakana" | "roman"
75114
* @returns 徽章文本
76115
*/
77-
export function getKatakanaBadge(index: number, useJapanese: boolean): string {
78-
if (useJapanese && index < JAPANESE_KATAKANA.length) {
79-
return JAPANESE_KATAKANA[index];
116+
export function getTOCBadge(index: number, badgeStyle: TOCBadgeStyle): string {
117+
switch (badgeStyle) {
118+
case "katakana":
119+
if (index < JAPANESE_KATAKANA.length) {
120+
return JAPANESE_KATAKANA[index];
121+
}
122+
return (index + 1).toString();
123+
case "roman":
124+
if (index < ROMAN_NUMERALS.length) {
125+
return ROMAN_NUMERALS[index];
126+
}
127+
return (index + 1).toString();
128+
case "number":
129+
default:
130+
return (index + 1).toString();
80131
}
81-
return (index + 1).toString();
132+
}
133+
134+
/**
135+
* 兼容旧接口:根据 useJapanese 布尔值获取徽章
136+
* @deprecated 请使用 getTOCBadge(index, badgeStyle) 代替
137+
*/
138+
export function getKatakanaBadge(index: number, useJapanese: boolean): string {
139+
return getTOCBadge(index, useJapanese ? "katakana" : "number");
82140
}
83141

84142
/**

src/components/features/toc/utils/toc-calculator.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import type { HeadingData, TOCItem } from "../types/toc";
7-
import { JAPANESE_KATAKANA } from "./japanese-katakana";
7+
import { getTOCBadge, type TOCBadgeStyle } from "./japanese-katakana";
88

99
/**
1010
* 计算最小标题级别
@@ -23,16 +23,13 @@ export function getBadgeText(
2323
index: number,
2424
level: number,
2525
minLevel: number,
26-
useJapaneseBadge: boolean,
26+
badgeStyle: TOCBadgeStyle,
2727
): string {
2828
if (level !== minLevel) {
2929
return "";
3030
}
3131

32-
if (useJapaneseBadge && index < JAPANESE_KATAKANA.length) {
33-
return JAPANESE_KATAKANA[index];
34-
}
35-
return (index + 1).toString();
32+
return getTOCBadge(index, badgeStyle);
3633
}
3734

3835
/**
@@ -41,7 +38,7 @@ export function getBadgeText(
4138
export function generateTOCItems(
4239
headings: HeadingData[],
4340
depth: number,
44-
useJapaneseBadge: boolean,
41+
badgeStyle: TOCBadgeStyle,
4542
): TOCItem[] {
4643
if (headings.length === 0) {
4744
return [];
@@ -58,7 +55,7 @@ export function generateTOCItems(
5855
h1Count,
5956
h.level,
6057
minLevel,
61-
useJapaneseBadge,
58+
badgeStyle,
6259
);
6360

6461
if (h.level === minLevel) {

src/components/features/toc/utils/toc-utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import type { HeadingData, TOCConfig, TOCItem } from "../types/toc";
6-
import { getKatakanaBadge } from "./japanese-katakana";
6+
import { getTOCBadge } from "./japanese-katakana";
77

88
/**
99
* 从 DOM 中提取标题数据
@@ -66,7 +66,7 @@ export function generateTOCItems(
6666
let badge: string | undefined;
6767

6868
if (h.level === minLevel) {
69-
badge = getKatakanaBadge(h1Count, config.useJapaneseBadge);
69+
badge = getTOCBadge(h1Count, config.badgeStyle);
7070
h1Count++;
7171
}
7272

@@ -133,6 +133,7 @@ export function getTOCConfig(): TOCConfig {
133133
enable: siteConfig.toc?.enable ?? true,
134134
mode: siteConfig.toc?.mode ?? "sidebar",
135135
depth: siteConfig.toc?.depth ?? 3,
136+
badgeStyle: siteConfig.toc?.badgeStyle ?? (siteConfig.toc?.useJapaneseBadge ? "katakana" : "number"),
136137
useJapaneseBadge: siteConfig.toc?.useJapaneseBadge ?? false,
137138
};
138139
}

0 commit comments

Comments
 (0)