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.
Files changed (141) hide show
  1. package/README.ja.md +132 -0
  2. package/README.md +142 -0
  3. package/components/KaTexReveal.vue +64 -0
  4. package/components/QuestionList.vue +252 -0
  5. package/components/TextBox.vue +109 -0
  6. package/dist/404.html +22 -0
  7. package/dist/_redirects +1 -0
  8. package/dist/assets/404-DpjNfLw1.css +1 -0
  9. package/dist/assets/DrawingControls-Cxk9a9ub.css +1 -0
  10. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  11. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  12. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  15. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  21. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  30. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  32. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  33. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  34. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  35. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  39. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  40. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  41. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  42. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  52. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  53. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  54. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  55. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  56. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  57. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  58. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  59. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  60. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  61. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  62. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  63. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  64. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  65. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  66. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  67. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  68. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  69. package/dist/assets/KaTexReveal-CPYYcbYL.js +261 -0
  70. package/dist/assets/KaTexReveal-DIQR78zC.css +1 -0
  71. package/dist/assets/NoteDisplay-DcLBOPDP.css +1 -0
  72. package/dist/assets/QuestionList-C2CV4WSw.js +56 -0
  73. package/dist/assets/QuestionList-CJKXL5Yq.css +1 -0
  74. package/dist/assets/SlideWrapper-B94O8a9G.css +1 -0
  75. package/dist/assets/entry-DeH8BBA6.css +1 -0
  76. package/dist/assets/index-BVsB-lMI.js +76 -0
  77. package/dist/assets/index-FlrlSOFu.css +1 -0
  78. package/dist/assets/link-Vmiqc0hc.js +1 -0
  79. package/dist/assets/logo-BYkHSa_O.png +0 -0
  80. package/dist/assets/md-8VraByb9.js +1 -0
  81. package/dist/assets/md-Bby241ZI.js +1 -0
  82. package/dist/assets/md-BqJwdePd.js +1 -0
  83. package/dist/assets/md-C0a547wx.js +3 -0
  84. package/dist/assets/md-C3C29Y15.css +1 -0
  85. package/dist/assets/md-CBmwngYy.js +1 -0
  86. package/dist/assets/md-DrNACvG6.js +3 -0
  87. package/dist/assets/md-DyJRgKOq.css +1 -0
  88. package/dist/assets/md-FY1NEgjs.js +1 -0
  89. package/dist/assets/md-RVqvqjSV.js +1 -0
  90. package/dist/assets/md-Un85CPx3.js +1 -0
  91. package/dist/assets/md-fq9C6Jm7.js +1 -0
  92. package/dist/assets/md-lcsTx_QH.css +1 -0
  93. package/dist/assets/md-nHLnZfr4.js +1 -0
  94. package/dist/assets/md-nyxGgl0C.js +1 -0
  95. package/dist/assets/md-rpN_jFpy.css +1 -0
  96. package/dist/assets/md-tn0RQdqM.css +0 -0
  97. package/dist/assets/modules/file-saver-BQwH87cS.js +1 -0
  98. package/dist/assets/modules/shiki-CNnpvYpa.js +1 -0
  99. package/dist/assets/modules/shiki-CozCpemh.css +1 -0
  100. package/dist/assets/modules/unplugin-icons-D4kCmVGc.js +1 -0
  101. package/dist/assets/modules/vue--QWtT9ew.js +40 -0
  102. package/dist/assets/play-D_7yfuao.css +1 -0
  103. package/dist/assets/presenter-BM7ZLO4O.css +1 -0
  104. package/dist/assets/shortcuts-DMsk7WYF.css +1 -0
  105. package/dist/assets/slidev/404-DWvVMASK.js +1 -0
  106. package/dist/assets/slidev/CodeBlockWrapper.vue_vue_type_script_setup_true_lang-DTUx4GGs.js +1 -0
  107. package/dist/assets/slidev/DrawingControls-kHEnsAlc.js +1 -0
  108. package/dist/assets/slidev/DrawingControls.vue_vue_type_style_index_0_lang-Bci_r_Jk.js +1 -0
  109. package/dist/assets/slidev/DrawingLayer-BGgDMhMm.js +1 -0
  110. package/dist/assets/slidev/IconButton.vue_vue_type_script_setup_true_lang-Di5pfAKC.js +1 -0
  111. package/dist/assets/slidev/NoteDisplay.vue_vue_type_style_index_0_lang-B00W6JVQ.js +1 -0
  112. package/dist/assets/slidev/SlideWrapper-n2BtjyCi.js +1 -0
  113. package/dist/assets/slidev/context-Db_2Hcpr.js +1 -0
  114. package/dist/assets/slidev/default-By-tXFz1.js +1 -0
  115. package/dist/assets/slidev/entry-BWgjvGUN.js +1 -0
  116. package/dist/assets/slidev/md-wS_Ed_8x.js +1 -0
  117. package/dist/assets/slidev/notes-Dv602Bu1.js +1 -0
  118. package/dist/assets/slidev/overview-BDs3_v07.js +1 -0
  119. package/dist/assets/slidev/play-CQ4qi9O0.js +2 -0
  120. package/dist/assets/slidev/presenter-CA_kPq3V.js +1 -0
  121. package/dist/assets/slidev/shortcuts-nzRDDQcI.js +4 -0
  122. package/dist/assets/useThemePalette-CeNaXTPl.js +1 -0
  123. package/dist/index.html +22 -0
  124. package/example.md +191 -0
  125. package/global-bottom.vue +70 -0
  126. package/layouts/cover.vue +64 -0
  127. package/layouts/end.vue +33 -0
  128. package/layouts/image-scroll.vue +138 -0
  129. package/layouts/image.vue +22 -0
  130. package/layouts/two-cols.vue +23 -0
  131. package/package.json +43 -0
  132. package/setup/shiki.ts +11 -0
  133. package/setup/shortcuts.ts +18 -0
  134. package/slidev-theme-watabegg-1.0.0.tgz +0 -0
  135. package/styles/globals.css +160 -0
  136. package/styles/index.ts +5 -0
  137. package/styles/layout.css +142 -0
  138. package/utils/colors.ts +57 -0
  139. package/utils/link.ts +17 -0
  140. package/utils/render.ts +5 -0
  141. package/utils/useThemePalette.ts +49 -0
package/README.ja.md ADDED
@@ -0,0 +1,132 @@
1
+ # slidev-theme-watabegg (日本語ドキュメント)
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/slidev-theme-watabegg?color=3AB9D4&label=)](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
+ ![フロントマター例](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/1.png)
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
+ ![two-cols 例](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/3.png)
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
+ ![QuestionList 例](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/4.png)
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
+ ![ユーティリティ例](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/7.png)
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
+ [![NPM version](https://img.shields.io/npm/v/slidev-theme-watabegg?color=3AB9D4&label=)](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
+ ![Frontmatter Example](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/1.png)
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
+ ![two-cols Example](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/3.png)
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
+ ![QuestionList Example](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/4.png)
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
+ ![Utility Example](https://raw.githubusercontent.com/watabegg/slidev-exam-prep/refs/heads/main/example/7.png)
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>