slidev-theme-watabegg 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +132 -0
- package/README.md +142 -0
- package/components/KaTexReveal.vue +64 -0
- package/components/QuestionList.vue +252 -0
- package/components/TextBox.vue +109 -0
- package/dist/404.html +22 -0
- package/dist/_redirects +1 -0
- package/dist/assets/404-DpjNfLw1.css +1 -0
- package/dist/assets/DrawingControls-Cxk9a9ub.css +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/KaTexReveal-CPYYcbYL.js +261 -0
- package/dist/assets/KaTexReveal-DIQR78zC.css +1 -0
- package/dist/assets/NoteDisplay-DcLBOPDP.css +1 -0
- package/dist/assets/QuestionList-C2CV4WSw.js +56 -0
- package/dist/assets/QuestionList-CJKXL5Yq.css +1 -0
- package/dist/assets/SlideWrapper-B94O8a9G.css +1 -0
- package/dist/assets/entry-DeH8BBA6.css +1 -0
- package/dist/assets/index-BVsB-lMI.js +76 -0
- package/dist/assets/index-FlrlSOFu.css +1 -0
- package/dist/assets/link-Vmiqc0hc.js +1 -0
- package/dist/assets/logo-BYkHSa_O.png +0 -0
- package/dist/assets/md-8VraByb9.js +1 -0
- package/dist/assets/md-Bby241ZI.js +1 -0
- package/dist/assets/md-BqJwdePd.js +1 -0
- package/dist/assets/md-C0a547wx.js +3 -0
- package/dist/assets/md-C3C29Y15.css +1 -0
- package/dist/assets/md-CBmwngYy.js +1 -0
- package/dist/assets/md-DrNACvG6.js +3 -0
- package/dist/assets/md-DyJRgKOq.css +1 -0
- package/dist/assets/md-FY1NEgjs.js +1 -0
- package/dist/assets/md-RVqvqjSV.js +1 -0
- package/dist/assets/md-Un85CPx3.js +1 -0
- package/dist/assets/md-fq9C6Jm7.js +1 -0
- package/dist/assets/md-lcsTx_QH.css +1 -0
- package/dist/assets/md-nHLnZfr4.js +1 -0
- package/dist/assets/md-nyxGgl0C.js +1 -0
- package/dist/assets/md-rpN_jFpy.css +1 -0
- package/dist/assets/md-tn0RQdqM.css +0 -0
- package/dist/assets/modules/file-saver-BQwH87cS.js +1 -0
- package/dist/assets/modules/shiki-CNnpvYpa.js +1 -0
- package/dist/assets/modules/shiki-CozCpemh.css +1 -0
- package/dist/assets/modules/unplugin-icons-D4kCmVGc.js +1 -0
- package/dist/assets/modules/vue--QWtT9ew.js +40 -0
- package/dist/assets/play-D_7yfuao.css +1 -0
- package/dist/assets/presenter-BM7ZLO4O.css +1 -0
- package/dist/assets/shortcuts-DMsk7WYF.css +1 -0
- package/dist/assets/slidev/404-DWvVMASK.js +1 -0
- package/dist/assets/slidev/CodeBlockWrapper.vue_vue_type_script_setup_true_lang-DTUx4GGs.js +1 -0
- package/dist/assets/slidev/DrawingControls-kHEnsAlc.js +1 -0
- package/dist/assets/slidev/DrawingControls.vue_vue_type_style_index_0_lang-Bci_r_Jk.js +1 -0
- package/dist/assets/slidev/DrawingLayer-BGgDMhMm.js +1 -0
- package/dist/assets/slidev/IconButton.vue_vue_type_script_setup_true_lang-Di5pfAKC.js +1 -0
- package/dist/assets/slidev/NoteDisplay.vue_vue_type_style_index_0_lang-B00W6JVQ.js +1 -0
- package/dist/assets/slidev/SlideWrapper-n2BtjyCi.js +1 -0
- package/dist/assets/slidev/context-Db_2Hcpr.js +1 -0
- package/dist/assets/slidev/default-By-tXFz1.js +1 -0
- package/dist/assets/slidev/entry-BWgjvGUN.js +1 -0
- package/dist/assets/slidev/md-wS_Ed_8x.js +1 -0
- package/dist/assets/slidev/notes-Dv602Bu1.js +1 -0
- package/dist/assets/slidev/overview-BDs3_v07.js +1 -0
- package/dist/assets/slidev/play-CQ4qi9O0.js +2 -0
- package/dist/assets/slidev/presenter-CA_kPq3V.js +1 -0
- package/dist/assets/slidev/shortcuts-nzRDDQcI.js +4 -0
- package/dist/assets/useThemePalette-CeNaXTPl.js +1 -0
- package/dist/index.html +22 -0
- package/example.md +191 -0
- package/global-bottom.vue +70 -0
- package/layouts/cover.vue +64 -0
- package/layouts/end.vue +33 -0
- package/layouts/image-scroll.vue +138 -0
- package/layouts/image.vue +22 -0
- package/layouts/two-cols.vue +23 -0
- package/package.json +43 -0
- package/setup/shiki.ts +11 -0
- package/setup/shortcuts.ts +18 -0
- package/slidev-theme-watabegg-1.0.0.tgz +0 -0
- package/styles/globals.css +160 -0
- package/styles/index.ts +5 -0
- package/styles/layout.css +142 -0
- package/utils/colors.ts +57 -0
- package/utils/link.ts +17 -0
- package/utils/render.ts +5 -0
- package/utils/useThemePalette.ts +49 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# slidev-theme-watabegg (日本語ドキュメント)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/slidev-theme-watabegg)
|
|
4
|
+
|
|
5
|
+
教育現場(集団授業・試験対策)向けに視認性と操作性を重視した Slidev テーマです。普通に自分用に開発しました。
|
|
6
|
+
|
|
7
|
+
## 特徴
|
|
8
|
+
- 教育特化のミニマルデザイン
|
|
9
|
+
- 日本語表示最適化 (M PLUS 2) / 等幅 Fira Code
|
|
10
|
+
- 自動フッター(`cover` / `image` 以外で日付 + ページ番号)
|
|
11
|
+
- ショートカット: Enter (次) / Backspace (前)
|
|
12
|
+
- レイアウト: `cover`, `two-cols`, `image`
|
|
13
|
+
- フロントマター `color` でテーマカラーを `red | yellow | green | blue | purple` から選択
|
|
14
|
+
- コンポーネント: `QuestionList`, `TextBox`, `KaTexReveal`
|
|
15
|
+
- 多段ラベル: 丸番号 / カタカナ / ひらがな / 漢数字(1–19) / 英字 / カスタム
|
|
16
|
+
- ユーティリティ: `.text-highlight`, `.card`
|
|
17
|
+
- Shiki テーマ: vitesse-light / vitesse-dark
|
|
18
|
+
|
|
19
|
+
## インストール
|
|
20
|
+
```yaml
|
|
21
|
+
---
|
|
22
|
+
theme: slidev-theme-watabegg
|
|
23
|
+
---
|
|
24
|
+
```
|
|
25
|
+
ローカル開発(クローンしたリポジトリで):
|
|
26
|
+
```yaml
|
|
27
|
+
---
|
|
28
|
+
theme: ./
|
|
29
|
+
---
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## フロントマター例
|
|
33
|
+
```yaml
|
|
34
|
+
title: 授業タイトル
|
|
35
|
+
subtitle: サブタイトル
|
|
36
|
+
author: 講師名
|
|
37
|
+
date: '2025/08/03'
|
|
38
|
+
color: green # red | yellow | green | blue | purple
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`color` を省略するとデフォルトの `green` が適用されます。
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## レイアウト
|
|
46
|
+
| 名称 | 用途 | 特徴 |
|
|
47
|
+
|------|------|------|
|
|
48
|
+
| cover | 表紙 | グラデーション波 + title/subtitle/author |
|
|
49
|
+
| two-cols | 2カラム | `::left::` / `::right::` スロット |
|
|
50
|
+
| image | 背景画像 | `image:` 指定 + `TextBox` で自由配置 |
|
|
51
|
+
|
|
52
|
+
### two-cols 例
|
|
53
|
+
```markdown
|
|
54
|
+
---
|
|
55
|
+
layout: two-cols
|
|
56
|
+
---
|
|
57
|
+
::left::
|
|
58
|
+
左
|
|
59
|
+
::right::
|
|
60
|
+
右
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
### image 例
|
|
66
|
+
```markdown
|
|
67
|
+
---
|
|
68
|
+
layout: image
|
|
69
|
+
image: /path/to/bg.jpg
|
|
70
|
+
---
|
|
71
|
+
<TextBox :x="120" :y="160" :width="360">注釈</TextBox>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## コンポーネント
|
|
75
|
+
### QuestionList
|
|
76
|
+
入れ子質問/解答・Markdown 埋め込み・階層別スタイル。
|
|
77
|
+
```vue
|
|
78
|
+
<QuestionList
|
|
79
|
+
:items="['最初 **OK**', { text: '2番目', items: ['子A','子B'] }]"
|
|
80
|
+
:styles="['decimal-circle','katakana-paren','loweralpha-dot']"
|
|
81
|
+
:start="[1,1,'c']"
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
カウンター種別: `decimal | hiragana | katakana | kanji | upperalpha | loweralpha | none`
|
|
85
|
+
デコレータ: `circle | square | paren | dot | q | big-q | none`
|
|
86
|
+
アイテム内 `label` があればそれを優先表示。
|
|
87
|
+
|
|
88
|
+

|
|
89
|
+
|
|
90
|
+
### TextBox
|
|
91
|
+
背景画像上などに絶対配置。
|
|
92
|
+
```vue
|
|
93
|
+
<TextBox :x="100" :y="220" :width="400" textBg="green" v-click="1">メモ</TextBox>
|
|
94
|
+
```
|
|
95
|
+
Props: `x`, `y`, `width`, `height`, `textBg`, `color`, `vClick`
|
|
96
|
+
|
|
97
|
+
### KaTexReveal
|
|
98
|
+
KaTeX API で数式を確実に描画するコンポーネント。`components/*.vue` は Slidev が自動登録するので、そのまま Markdown で利用できます。
|
|
99
|
+
```vue
|
|
100
|
+
<KaTexReveal formula="\\int_0^{2\\pi} \\sin x\\,dx = 0" block class="text-2xl" />
|
|
101
|
+
<KaTexReveal formula="E = mc^2" :block="false" v-click="1" />
|
|
102
|
+
```
|
|
103
|
+
Props: `formula`(必須), `block`(既定 false), `tag`(省略時 `div`/`span` 自動), そのほか `class` や `v-click` など任意の属性も転送。`QuestionList` のアイテムに TeX が含まれる場合もこのコンポーネントで描画されます。
|
|
104
|
+
|
|
105
|
+
## フッター & ショートカット
|
|
106
|
+
- フッター: (cover/image 以外) `date` + 現在ページ/総ページ
|
|
107
|
+
- Enter: 次のフラグメント/スライド
|
|
108
|
+
- Backspace: 前へ
|
|
109
|
+
|
|
110
|
+
## ユーティリティクラス
|
|
111
|
+
- `.text-highlight` 行マーカ風ハイライト
|
|
112
|
+
- `.card` 角丸ボックス + 余白 + 枠線
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
## 開発
|
|
117
|
+
```bash
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm dev
|
|
120
|
+
pnpm build
|
|
121
|
+
pnpm export
|
|
122
|
+
pnpm screenshot
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## FAQ
|
|
126
|
+
**Q. フォント設定は必要?** → いいえ、テーマ内で Google Fonts を読み込みます。
|
|
127
|
+
|
|
128
|
+
**Q. フッターを消したい** → 自作テーマで `global-bottom.vue` を上書きしてください。
|
|
129
|
+
|
|
130
|
+
**Q. 番号開始位置を変えたい** → `:start="[1,'c']"` のように配列で指定。
|
|
131
|
+
|
|
132
|
+
良い授業を! 🎓
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# slidev-theme-watabegg
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/slidev-theme-watabegg)
|
|
4
|
+
|
|
5
|
+
English README (Japanese version: see `README.ja.md`).
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
An education-focused Slidev theme for exam preparation & classroom lessons, optimized for Japanese typography, high readability, structured question hierarchies, and keyboard-centric navigation. Originally built for personal teaching use.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
- Minimal classroom-oriented design
|
|
12
|
+
- Japanese font (M PLUS 2) + monospace Fira Code
|
|
13
|
+
- Automatic footer (date + current/total page) except on `cover` / `image`
|
|
14
|
+
- Shortcuts: Enter (next) / Backspace (previous)
|
|
15
|
+
- Layouts: `cover`, `two-cols`, `image`
|
|
16
|
+
- Frontmatter `color` switch for `red | yellow | green | blue | purple`
|
|
17
|
+
- Components: `QuestionList`, `TextBox`, `KaTexReveal`
|
|
18
|
+
- Multi-level labels: circled numbers / Katakana / Hiragana / kanji numerals (1–19) / alphabet / custom
|
|
19
|
+
- Utility classes: `.text-highlight`, `.card`
|
|
20
|
+
- Shiki code themes: `vitesse-light` / `vitesse-dark`
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
Add to your slide frontmatter:
|
|
24
|
+
```yaml
|
|
25
|
+
---
|
|
26
|
+
theme: slidev-theme-watabegg
|
|
27
|
+
---
|
|
28
|
+
```
|
|
29
|
+
Local development (cloned repository):
|
|
30
|
+
```yaml
|
|
31
|
+
---
|
|
32
|
+
theme: ./
|
|
33
|
+
---
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Frontmatter Example
|
|
37
|
+
```yaml
|
|
38
|
+
title: Class Title
|
|
39
|
+
subtitle: Subtitle
|
|
40
|
+
author: Instructor Name
|
|
41
|
+
date: '2025/08/03'
|
|
42
|
+
color: green # red | yellow | green | blue | purple
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If `color` is omitted, the default `green` palette is used.
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Layouts
|
|
50
|
+
| Name | Purpose | Notes |
|
|
51
|
+
|------|---------|-------|
|
|
52
|
+
| cover | Title slide | Gradient wave + title/subtitle/author |
|
|
53
|
+
| two-cols | Two columns | Slots: `::left::` / `::right::` |
|
|
54
|
+
| image | Background image | Use `image:` + absolutely placed `TextBox` |
|
|
55
|
+
|
|
56
|
+
### two-cols Example
|
|
57
|
+
```markdown
|
|
58
|
+
---
|
|
59
|
+
layout: two-cols
|
|
60
|
+
---
|
|
61
|
+
::left::
|
|
62
|
+
Left
|
|
63
|
+
::right::
|
|
64
|
+
Right
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
### image Example
|
|
70
|
+
```markdown
|
|
71
|
+
---
|
|
72
|
+
layout: image
|
|
73
|
+
image: /path/to/bg.jpg
|
|
74
|
+
---
|
|
75
|
+
<TextBox :x="120" :y="160" :width="360">Note</TextBox>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Components
|
|
79
|
+
### QuestionList
|
|
80
|
+
Recursive nested lists (questions / answers) with per-level styles and Markdown rendering.
|
|
81
|
+
```vue
|
|
82
|
+
<QuestionList
|
|
83
|
+
:items="['First **OK**', { text: 'Second', items: ['Child A','Child B'] }]"
|
|
84
|
+
:styles="['decimal-circle','katakana-paren','loweralpha-dot']"
|
|
85
|
+
:start="[1,1,'c']"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
Counter types: `decimal | hiragana | katakana | kanji | upperalpha | loweralpha | none`
|
|
89
|
+
Decorators: `circle | square | paren | dot | q | big-q | none`
|
|
90
|
+
If an item includes a `label`, it overrides the computed label.
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
### TextBox
|
|
95
|
+
Absolutely positioned overlay (useful on `image` layout or for callouts).
|
|
96
|
+
```vue
|
|
97
|
+
<TextBox :x="100" :y="220" :width="400" textBg="green" v-click="1">Memo</TextBox>
|
|
98
|
+
```
|
|
99
|
+
Props: `x`, `y`, `width`, `height`, `textBg`, `color`, `vClick`
|
|
100
|
+
|
|
101
|
+
### KaTexReveal
|
|
102
|
+
Inline/Block KaTeX renderer component. Slidev auto-registers `components/*.vue`, so you can drop it directly into Markdown.
|
|
103
|
+
```vue
|
|
104
|
+
<KaTexReveal formula="\\int_0^{2\\pi} \\sin x\\,dx = 0" block class="text-2xl" />
|
|
105
|
+
<KaTexReveal formula="E = mc^2" :block="false" v-click="1" />
|
|
106
|
+
```
|
|
107
|
+
Props: `formula` (required string), `block` (default `false`), `tag` (fallbacks to `div`/`span`), plus any other attrs (`class`, `v-click`, etc.) forwarded to the wrapper. Also consumed by `QuestionList` when an item contains TeX delimiters, `formula`, or `tex: true`.
|
|
108
|
+
|
|
109
|
+
## Footer & Shortcuts
|
|
110
|
+
- Footer (except on cover / image): displays `date` + current / total pages.
|
|
111
|
+
- Enter: next fragment or slide.
|
|
112
|
+
- Backspace: previous fragment or slide.
|
|
113
|
+
|
|
114
|
+
## Utility Classes
|
|
115
|
+
- `.text-highlight` inline / line-marker style highlight
|
|
116
|
+
- `.card` rounded bordered container with padding
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
```bash
|
|
122
|
+
pnpm install
|
|
123
|
+
pnpm dev
|
|
124
|
+
pnpm build
|
|
125
|
+
pnpm export
|
|
126
|
+
pnpm screenshot
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## FAQ
|
|
130
|
+
**Q. Do I need to configure fonts manually?**
|
|
131
|
+
No. Google Fonts are loaded inside the theme.
|
|
132
|
+
|
|
133
|
+
**Q. How can I remove the footer?**
|
|
134
|
+
Override `global-bottom.vue` in your own custom theme.
|
|
135
|
+
|
|
136
|
+
**Q. How do I change the numbering start?**
|
|
137
|
+
Use `:start="[1,'c']"` (array per hierarchy level).
|
|
138
|
+
|
|
139
|
+
## See Also
|
|
140
|
+
Japanese documentation: `README.ja.md`
|
|
141
|
+
|
|
142
|
+
Happy teaching! 🎓
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="resolvedTag"
|
|
4
|
+
ref="root"
|
|
5
|
+
v-bind="forwardedAttrs"
|
|
6
|
+
:class="['katex-reveal', attrs.class]"
|
|
7
|
+
/>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { computed, onMounted, onUpdated, ref, useAttrs } from 'vue';
|
|
12
|
+
import katex from 'katex';
|
|
13
|
+
import 'katex/dist/katex.min.css';
|
|
14
|
+
|
|
15
|
+
defineOptions({ inheritAttrs: false });
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<{
|
|
18
|
+
formula: string;
|
|
19
|
+
block?: boolean;
|
|
20
|
+
tag?: string;
|
|
21
|
+
}>(), {
|
|
22
|
+
block: false,
|
|
23
|
+
tag: undefined,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const attrs = useAttrs();
|
|
27
|
+
const root = ref<HTMLElement | null>(null);
|
|
28
|
+
|
|
29
|
+
const forwardedAttrs = computed(() => {
|
|
30
|
+
const { class: _class, ...rest } = attrs as Record<string, any>;
|
|
31
|
+
return rest;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const resolvedTag = computed(() => props.tag ?? (props.block ? 'div' : 'span'));
|
|
35
|
+
|
|
36
|
+
const renderFormula = () => {
|
|
37
|
+
const target = root.value;
|
|
38
|
+
if (!target) return;
|
|
39
|
+
|
|
40
|
+
if (!props.formula) {
|
|
41
|
+
target.innerHTML = '';
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
katex.render(props.formula, target, {
|
|
47
|
+
displayMode: props.block,
|
|
48
|
+
throwOnError: false,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn('[KaTexReveal] failed to render formula', error);
|
|
52
|
+
target.textContent = props.formula;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
onMounted(renderFormula);
|
|
57
|
+
onUpdated(renderFormula);
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<style scoped>
|
|
61
|
+
.katex-reveal {
|
|
62
|
+
vertical-align: middle;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="question-list" :style="{ '--level': currentLevel }">
|
|
3
|
+
<div v-for="(item, index) in items" :key="index" class="question-item">
|
|
4
|
+
<div class="item-content">
|
|
5
|
+
<span class="item-label" v-html="getLabel(index)"></span>
|
|
6
|
+
<span v-if="hasRenderableContent(item)" class="item-text">
|
|
7
|
+
<KaTexReveal
|
|
8
|
+
v-if="isKatexItem(item)"
|
|
9
|
+
:formula="getFormula(item)"
|
|
10
|
+
:block="isBlockFormula(item)"
|
|
11
|
+
v-bind="getKatexAttrs(item)"
|
|
12
|
+
/>
|
|
13
|
+
<component v-else :is="renderMarkdown(getItemText(item))" />
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div v-if="hasSubItems(item)" class="sub-list" :style="{ marginTop: getItemText(item) ? '1rem' : '0' }">
|
|
17
|
+
<QuestionList
|
|
18
|
+
:items="getSubItems(item)"
|
|
19
|
+
:styles="styles"
|
|
20
|
+
:level="currentLevel + 1"
|
|
21
|
+
:start="Array.isArray(start) ? start : []"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { computed } from 'vue';
|
|
30
|
+
import KaTexReveal from './KaTexReveal.vue';
|
|
31
|
+
import { renderMarkdown } from '../utils/render';
|
|
32
|
+
|
|
33
|
+
const props = withDefaults(defineProps<{
|
|
34
|
+
items: (string | Record<string, any>)[];
|
|
35
|
+
styles?: string[];
|
|
36
|
+
level?: number;
|
|
37
|
+
start?: (number | string)[];
|
|
38
|
+
}>(), {
|
|
39
|
+
styles: () => ['decimal-circle', 'katakana-paren', 'lower-alpha-paren', 'decimal-dot'],
|
|
40
|
+
level: 0,
|
|
41
|
+
start: () => [],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const currentLevel = computed(() => props.level ?? 0);
|
|
45
|
+
|
|
46
|
+
const TEX_WRAPPERS = [
|
|
47
|
+
{ start: '$$', end: '$$' },
|
|
48
|
+
{ start: '\\[', end: '\\]' },
|
|
49
|
+
{ start: '\\(', end: '\\)' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const stripTexDelimiters = (value: string): string => {
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
for (const { start, end } of TEX_WRAPPERS) {
|
|
55
|
+
if (trimmed.startsWith(start) && trimmed.endsWith(end)) {
|
|
56
|
+
return trimmed.slice(start.length, trimmed.length - end.length).trim();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return trimmed;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const extractFormulaFromText = (value?: string): string => {
|
|
63
|
+
if (!value) return '';
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
for (const { start, end } of TEX_WRAPPERS) {
|
|
66
|
+
if (trimmed.startsWith(start) && trimmed.endsWith(end)) {
|
|
67
|
+
return stripTexDelimiters(trimmed);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const hasRenderableContent = (item: string | Record<string, any>): boolean => {
|
|
74
|
+
if (isKatexItem(item)) return true;
|
|
75
|
+
return Boolean(getItemText(item).trim());
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const normalizedItemType = (item: Record<string, any>): string => {
|
|
79
|
+
if (typeof item.type !== 'string') return '';
|
|
80
|
+
return item.type.toLowerCase();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getFormula = (item: string | Record<string, any>): string => {
|
|
84
|
+
if (typeof item === 'object') {
|
|
85
|
+
if (typeof item.formula === 'string' && item.formula.trim()) {
|
|
86
|
+
return stripTexDelimiters(item.formula);
|
|
87
|
+
}
|
|
88
|
+
const hint = item.tex === true || ['katex', 'tex', 'math'].includes(normalizedItemType(item));
|
|
89
|
+
if (hint && typeof item.text === 'string') {
|
|
90
|
+
return stripTexDelimiters(item.text);
|
|
91
|
+
}
|
|
92
|
+
const extracted = extractFormulaFromText(item.text);
|
|
93
|
+
return extracted;
|
|
94
|
+
}
|
|
95
|
+
return extractFormulaFromText(item) || '';
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const isKatexItem = (item: string | Record<string, any>): boolean => {
|
|
99
|
+
return Boolean(getFormula(item));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const isBlockFormula = (item: string | Record<string, any>): boolean => {
|
|
103
|
+
if (typeof item === 'object' && typeof item.block === 'boolean') {
|
|
104
|
+
return item.block;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const source = typeof item === 'string'
|
|
108
|
+
? item
|
|
109
|
+
: typeof item.formula === 'string'
|
|
110
|
+
? item.formula
|
|
111
|
+
: typeof item.text === 'string'
|
|
112
|
+
? item.text
|
|
113
|
+
: '';
|
|
114
|
+
const trimmed = source.trim();
|
|
115
|
+
if (!trimmed) return true;
|
|
116
|
+
if (trimmed.startsWith('\\(') && trimmed.endsWith('\\)')) return false;
|
|
117
|
+
if (trimmed.startsWith('$') && trimmed.endsWith('$') && !trimmed.startsWith('$$')) return false;
|
|
118
|
+
if (trimmed.startsWith('$$') && trimmed.endsWith('$$')) return true;
|
|
119
|
+
if (trimmed.startsWith('\\[') && trimmed.endsWith('\\]')) return true;
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const reservedItemKeys = new Set(['text', 'items', 'label', 'block', 'type', 'tex', 'formula', 'attrs']);
|
|
124
|
+
|
|
125
|
+
const getKatexAttrs = (item: string | Record<string, any>): Record<string, any> => {
|
|
126
|
+
if (typeof item !== 'object') {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
if (item.attrs && typeof item.attrs === 'object') {
|
|
130
|
+
return item.attrs;
|
|
131
|
+
}
|
|
132
|
+
const attrs: Record<string, any> = {};
|
|
133
|
+
Object.keys(item).forEach((key) => {
|
|
134
|
+
if (!reservedItemKeys.has(key)) {
|
|
135
|
+
attrs[key] = item[key];
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return attrs;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const getItemText = (item: string | Record<string, any>): string =>
|
|
142
|
+
(typeof item === 'string' ? item : item.text) || '';
|
|
143
|
+
|
|
144
|
+
const hasSubItems = (item: string | Record<string, any>): boolean =>
|
|
145
|
+
typeof item === 'object' && Array.isArray(item.items);
|
|
146
|
+
|
|
147
|
+
const getSubItems = (item: string | Record<string, any>): any[] =>
|
|
148
|
+
(typeof item === 'object' && Array.isArray(item.items)) ? item.items : [];
|
|
149
|
+
|
|
150
|
+
const getLabel = (index: number): string => {
|
|
151
|
+
const item = props.items[index];
|
|
152
|
+
// 1. Custom item-specific labels override everything
|
|
153
|
+
if (typeof item === 'object' && typeof item.label === 'string') {
|
|
154
|
+
return item.label;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const style = props.styles[currentLevel.value] || 'decimal-dot';
|
|
158
|
+
const [counterType, decoratorType] = style.split('-');
|
|
159
|
+
|
|
160
|
+
// 2. Calculate the counter
|
|
161
|
+
const startIndex = props.start[currentLevel.value] ? (typeof props.start[currentLevel.value] === 'number' ? (props.start[currentLevel.value] as number) - 1 : String(props.start[currentLevel.value]).charCodeAt(0) - 'a'.charCodeAt(0)) : 0;
|
|
162
|
+
const currentIndex = startIndex + index;
|
|
163
|
+
|
|
164
|
+
let counter = '';
|
|
165
|
+
switch (counterType) {
|
|
166
|
+
case 'hiragana':
|
|
167
|
+
counter = toHiragana(currentIndex); break;
|
|
168
|
+
case 'katakana':
|
|
169
|
+
counter = toKatakana(currentIndex); break;
|
|
170
|
+
case 'kanji':
|
|
171
|
+
counter = toKanji(currentIndex + 1); break;
|
|
172
|
+
case 'upperalpha':
|
|
173
|
+
counter = String.fromCharCode('A'.charCodeAt(0) + currentIndex); break;
|
|
174
|
+
case 'loweralpha':
|
|
175
|
+
counter = String.fromCharCode('a'.charCodeAt(0) + currentIndex); break;
|
|
176
|
+
case 'decimal':
|
|
177
|
+
counter = (currentIndex + 1).toString(); break;
|
|
178
|
+
case 'none':
|
|
179
|
+
return ''; // No label
|
|
180
|
+
default:
|
|
181
|
+
counter = (currentIndex + 1).toString();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 3. Apply decorator
|
|
185
|
+
switch (decoratorType) {
|
|
186
|
+
case 'circle':
|
|
187
|
+
return `&#x${(9311 + currentIndex + 1).toString(16)};`; // ①, ②, ...
|
|
188
|
+
case 'square':
|
|
189
|
+
return `[${counter}]`;
|
|
190
|
+
case 'paren':
|
|
191
|
+
return `(${counter})`;
|
|
192
|
+
case 'dot':
|
|
193
|
+
return `${counter}.`;
|
|
194
|
+
case 'q':
|
|
195
|
+
return `問${counter}`;
|
|
196
|
+
case 'big-q':
|
|
197
|
+
return `大問${counter}`;
|
|
198
|
+
case 'none':
|
|
199
|
+
return '';
|
|
200
|
+
default:
|
|
201
|
+
return counter;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// --- Helper functions for counters ---
|
|
206
|
+
const toKatakana = (n: number) => {
|
|
207
|
+
const katakana = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン';
|
|
208
|
+
return katakana[n % katakana.length];
|
|
209
|
+
};
|
|
210
|
+
const toHiragana = (n: number) => {
|
|
211
|
+
const hiragana = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん';
|
|
212
|
+
return hiragana[n % hiragana.length];
|
|
213
|
+
};
|
|
214
|
+
const toKanji = (n: number) => {
|
|
215
|
+
const kanji = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
|
|
216
|
+
if (n <= 10) return kanji[n - 1];
|
|
217
|
+
// Basic support for > 10
|
|
218
|
+
if (n <= 19) return '十' + kanji[n - 11];
|
|
219
|
+
return n.toString(); // Fallback
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
.question-list {
|
|
226
|
+
padding-left: calc(var(--level, 0) * 2.5rem);
|
|
227
|
+
}
|
|
228
|
+
.question-item {
|
|
229
|
+
margin-bottom: 0.6rem;
|
|
230
|
+
}
|
|
231
|
+
.item-content {
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: baseline;
|
|
234
|
+
line-height: 1.6;
|
|
235
|
+
}
|
|
236
|
+
.item-label {
|
|
237
|
+
font-weight: bold;
|
|
238
|
+
color: var(--slidev-theme-primary);
|
|
239
|
+
margin-right: 0.75em;
|
|
240
|
+
min-width: 2em; /* Allocate space for label */
|
|
241
|
+
text-align: right;
|
|
242
|
+
}
|
|
243
|
+
.item-text {
|
|
244
|
+
flex: 1;
|
|
245
|
+
}
|
|
246
|
+
.item-text :deep(p) {
|
|
247
|
+
margin: 0;
|
|
248
|
+
}
|
|
249
|
+
.sub-list {
|
|
250
|
+
margin-top: 1rem;
|
|
251
|
+
}
|
|
252
|
+
</style>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="textbox"
|
|
4
|
+
:class="computedClasses"
|
|
5
|
+
:style="computedStyles"
|
|
6
|
+
v-click="vClick"
|
|
7
|
+
>
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
x?: number
|
|
17
|
+
y?: number
|
|
18
|
+
width?: number
|
|
19
|
+
height?: number
|
|
20
|
+
color?: string
|
|
21
|
+
textBg?: string
|
|
22
|
+
vClick?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
width: 300,
|
|
29
|
+
height: undefined,
|
|
30
|
+
color: undefined,
|
|
31
|
+
textBg: undefined,
|
|
32
|
+
vClick: undefined
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const computedStyles = computed(() => ({
|
|
36
|
+
left: `${props.x}px`,
|
|
37
|
+
top: `${props.y}px`,
|
|
38
|
+
width: `${props.width}px`,
|
|
39
|
+
...(props.height && { height: `${props.height}px` })
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const computedClasses = computed(() => {
|
|
43
|
+
const classes: string[] = []
|
|
44
|
+
|
|
45
|
+
if (props.textBg) {
|
|
46
|
+
classes.push(`bg-${props.textBg}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (props.color) {
|
|
50
|
+
classes.push(`text-${props.color}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return classes
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style scoped>
|
|
58
|
+
.textbox {
|
|
59
|
+
position: absolute;
|
|
60
|
+
background-color: rgba(255, 255, 255, 0);
|
|
61
|
+
padding: 0.6rem;
|
|
62
|
+
border-radius: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.bg-green {
|
|
66
|
+
background: rgba(76, 175, 80);
|
|
67
|
+
color: white;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.bg-blue {
|
|
71
|
+
background: rgba(33, 150, 243);
|
|
72
|
+
color: white;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.bg-red {
|
|
76
|
+
background: rgba(244, 67, 54);
|
|
77
|
+
color: white;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.bg-yellow {
|
|
81
|
+
background: rgba(255, 193, 7);
|
|
82
|
+
color: #333;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.bg-white {
|
|
86
|
+
background: rgba(255, 255, 255);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Text color variants */
|
|
90
|
+
.text-blue {
|
|
91
|
+
color: #3b82f6 !important;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.text-green {
|
|
95
|
+
color: #10b981 !important;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.text-red {
|
|
99
|
+
color: #ef4444 !important;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.text-purple {
|
|
103
|
+
color: #8b5cf6 !important;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.text-orange {
|
|
107
|
+
color: #f97316 !important;
|
|
108
|
+
}
|
|
109
|
+
</style>
|