slidev-theme-gtlabo 1.1.1 → 2.0.1

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.
@@ -0,0 +1,93 @@
1
+ <!-- components/ChapterItem.vue -->
2
+ <template>
3
+ <div
4
+ class="flex flex-col"
5
+ :class="isNext ? 'border-sky-500 border-l-8 pl-6' : 'border-slate-200 border-l-4 pl-7'"
6
+ >
7
+ <!-- 次の章インジケーター - sky-500アクセント -->
8
+ <div
9
+ v-if="isNext"
10
+ :class="nextBadgeClasses"
11
+ class="bg-sky-500 text-white font-bold uppercase tracking-widest leading-none"
12
+ >
13
+ NEXT
14
+ </div>
15
+
16
+ <!-- 章番号とタイトル - 左揃え、明確なヒエラルキー -->
17
+ <div :class="chapterHeaderClasses">
18
+ <div class="flex items-end space-x-3 w-full">
19
+ <div :class="chapterNumberClasses" class="flex text-slate-900 font-bold tabular-nums leading-none">
20
+ {{ chapterNumber }}
21
+ </div>
22
+ <div class="flex-1">
23
+ <div class="border-b border-slate-900 mb-2">
24
+ <span :class="chapterTitleClasses" class="text-slate-900 font-bold leading-tight">
25
+ {{ chapter.title }}
26
+ </span>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- セクション一覧 - 機能的な情報構造 -->
33
+ <div :class="sectionListClasses">
34
+ <div
35
+ v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
36
+ :key="`${chapter.key}-${sectionIndex}`"
37
+ class="flex items-start"
38
+ >
39
+ <span class="w-1 h-1 bg-slate-400 mr-2 mt-1.5 flex-shrink-0 rounded-full"></span>
40
+ <span :class="sectionTextClasses" class="text-slate-600 leading-snug">
41
+ {{ sectionData.title }}
42
+ </span>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup>
49
+ import { computed } from 'vue'
50
+
51
+ const props = defineProps({
52
+ chapter: {
53
+ type: Object,
54
+ required: true
55
+ },
56
+ index: {
57
+ type: Number,
58
+ required: true
59
+ },
60
+ isNext: {
61
+ type: Boolean,
62
+ default: false
63
+ },
64
+ isAppendix: {
65
+ type: Boolean,
66
+ default: false
67
+ },
68
+ size: {
69
+ type: String,
70
+ default: 'md'
71
+ },
72
+ config: {
73
+ type: Object,
74
+ required: true
75
+ }
76
+ })
77
+
78
+ // 章番号の生成(通常章は "01", "02"、付録は "A1", "A2")
79
+ const chapterNumber = computed(() => {
80
+ if (props.isAppendix) {
81
+ return `A${props.index + 1}`
82
+ }
83
+ return String(props.index + 1).padStart(2, '0')
84
+ })
85
+
86
+ // 動的クラス
87
+ const chapterHeaderClasses = computed(() => props.config.chapterHeader)
88
+ const chapterNumberClasses = computed(() => props.config.chapterNumber)
89
+ const chapterTitleClasses = computed(() => props.config.chapterTitle)
90
+ const sectionListClasses = computed(() => props.config.sectionList)
91
+ const sectionTextClasses = computed(() => props.config.sectionText)
92
+ const nextBadgeClasses = computed(() => props.config.nextBadge)
93
+ </script>
@@ -1,24 +1,350 @@
1
+ <!-- components/TableOfContents.vue -->
1
2
  <template>
2
- <!-- セクション区切り用の目次ページ -->
3
- <TableOfContents
4
- :next-chapter="nextChapter"
5
- :current-chapter="currentChapter"
6
- />
3
+ <div class="table-of-contents-container flex flex-col justify-center bg-theme-color">
4
+ <!-- ヘッダー -->
5
+ <div
6
+ v-if="isAppendixSection"
7
+ :class="headerClasses"
8
+ >
9
+ <p :class="headerTitleClasses">
10
+ 付録
11
+ </p>
12
+ </div>
13
+
14
+ <!-- メインコンテンツ -->
15
+ <div :class="mainContentClasses">
16
+ <div class="max-w-full w-full">
17
+ <!-- 通常の目次表示 -->
18
+ <div
19
+ v-if="!isAppendixSection"
20
+ :class="gridClasses"
21
+ >
22
+ <div
23
+ v-for="(chapter, index) in regularChapters"
24
+ :key="chapter.key"
25
+ class="flex flex-col"
26
+ :class="{ 'relative': isNextChapter(chapter.key) }"
27
+ >
28
+ <!-- 次の章の強調表示 -->
29
+ <div
30
+ v-if="isNextChapter(chapter.key)"
31
+ :class="highlightBorderClasses"
32
+ />
33
+ <div
34
+ v-if="isNextChapter(chapter.key)"
35
+ :class="nextBadgeClasses"
36
+ >
37
+ NEXT
38
+ </div>
39
+
40
+ <!-- 章番号とタイトル -->
41
+ <div :class="chapterHeaderClasses">
42
+ <div :class="chapterNumberClasses">
43
+ {{ String(index + 1).padStart(2, '0') }}
44
+ </div>
45
+ <div class="flex-1 border-b border-white pb-2">
46
+ <span :class="chapterTitleClasses">
47
+ {{ chapter.title }}
48
+ </span>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- セクション一覧(重複除去済み) -->
53
+ <div :class="sectionListClasses">
54
+ <div
55
+ v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
56
+ :key="`${chapter.key}-${sectionIndex}`"
57
+ :class="sectionItemClasses"
58
+ >
59
+ <span :class="sectionTextClasses">
60
+ {{ sectionData.title }}
61
+ </span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- 付録の目次表示 -->
68
+ <div
69
+ v-else
70
+ :class="gridClasses"
71
+ >
72
+ <div
73
+ v-for="(chapter, index) in appendixChapters"
74
+ :key="chapter.key"
75
+ class="flex flex-col"
76
+ :class="{ 'relative': isNextChapter(chapter.key) }"
77
+ >
78
+ <!-- 次の章の強調表示 -->
79
+ <div
80
+ v-if="isNextChapter(chapter.key)"
81
+ :class="highlightBorderClasses"
82
+ />
83
+ <div
84
+ v-if="isNextChapter(chapter.key)"
85
+ :class="nextBadgeClasses"
86
+ >
87
+ NEXT
88
+ </div>
89
+
90
+ <!-- 付録番号とタイトル -->
91
+ <div :class="chapterHeaderClasses">
92
+ <div :class="appendixNumberClasses">
93
+ A{{ index + 1 }}
94
+ </div>
95
+ <div class="flex-1 border-b border-white pb-2">
96
+ <h2 :class="chapterTitleClasses">
97
+ {{ chapter.title }}
98
+ </h2>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- セクション一覧(重複除去済み) -->
103
+ <div :class="sectionListClasses">
104
+ <div
105
+ v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
106
+ :key="`${chapter.key}-${sectionIndex}`"
107
+ :class="sectionItemClasses"
108
+ >
109
+ <span :class="sectionTextClasses">
110
+ {{ sectionData.title }}
111
+ </span>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
7
119
  </template>
8
-
120
+
9
121
  <script setup>
10
- import TableOfContents from './TableOfContents.vue'
122
+ import { computed } from 'vue'
123
+ import { useSlideContext } from '@slidev/client'
124
+
125
+ // Slidevコンテキストからconfigsにアクセス
126
+ const { $slidev } = useSlideContext()
127
+ const chapters = $slidev.configs.chapters || {}
11
128
 
12
129
  const props = defineProps({
13
- nextChapter: {
130
+ nextChapter: {
14
131
  type: String,
15
132
  required: true,
16
- description: '次に始まる章のキー(例: "c2", "c3", "ap1")'
17
- },
18
- currentChapter: {
133
+ description: '次に始まる章のキー'
134
+ },
135
+ currentChapter: {
19
136
  type: String,
20
137
  required: false,
21
138
  description: '現在の章のキー(進捗表示用)'
139
+ },
140
+ size: {
141
+ type: String,
142
+ default: 'md',
143
+ validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
144
+ description: '目次のサイズ(xs, sm, md, lg, xl)'
145
+ }
146
+ })
147
+
148
+ // セクションの重複を除去する関数
149
+ const getUniqueSections = (chapterKey) => {
150
+ const chapter = chapters[chapterKey]
151
+ if (!chapter?.sections) return []
152
+
153
+ const sections = chapter.sections
154
+ const sectionKeys = Object.keys(sections)
155
+ const uniqueSections = []
156
+ let previousTitle = null
157
+
158
+ for (const sectionKey of sectionKeys) {
159
+ const sectionData = sections[sectionKey]
160
+ const currentTitle = sectionData?.title || `セクション ${sectionKey}`
161
+
162
+ // 前のセクションと同じタイトルでない場合のみ追加
163
+ if (currentTitle !== previousTitle) {
164
+ uniqueSections.push({
165
+ key: sectionKey,
166
+ title: currentTitle,
167
+ originalData: sectionData
168
+ })
169
+ previousTitle = currentTitle
170
+ }
171
+ }
172
+
173
+ return uniqueSections
22
174
  }
175
+
176
+ // 次の章が付録かどうかを判定
177
+ const isAppendixSection = computed(() => {
178
+ return props.nextChapter.startsWith('ap')
23
179
  })
24
- </script>
180
+
181
+ // 通常の章リスト(refと付録を除外)
182
+ const regularChapters = computed(() => {
183
+ if (!chapters || typeof chapters !== 'object') {
184
+ return []
185
+ }
186
+
187
+ return Object.keys(chapters)
188
+ .filter(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
189
+ .map(chapterKey => {
190
+ const chapter = chapters[chapterKey]
191
+ const sections = chapter?.sections || {}
192
+ const sectionKeys = Object.keys(sections)
193
+
194
+ return {
195
+ key: chapterKey,
196
+ title: chapter?.title || `章 ${chapterKey}`,
197
+ sections: sectionKeys,
198
+ sectionData: sections,
199
+ uniqueSections: getUniqueSections(chapterKey) // 重複除去済みセクション
200
+ }
201
+ })
202
+ })
203
+
204
+ // 付録の章リスト
205
+ const appendixChapters = computed(() => {
206
+ if (!chapters || typeof chapters !== 'object') {
207
+ return []
208
+ }
209
+
210
+ return Object.keys(chapters)
211
+ .filter(chapterKey => chapterKey.startsWith('ap'))
212
+ .map(chapterKey => {
213
+ const chapter = chapters[chapterKey]
214
+ const sections = chapter?.sections || {}
215
+ const sectionKeys = Object.keys(sections)
216
+
217
+ return {
218
+ key: chapterKey,
219
+ title: chapter?.title || `付録 ${chapterKey}`,
220
+ sections: sectionKeys,
221
+ sectionData: sections,
222
+ uniqueSections: getUniqueSections(chapterKey) // 重複除去済みセクション
223
+ }
224
+ })
225
+ })
226
+
227
+ // セクションタイトルを取得
228
+ const getSectionTitle = (chapterKey, sectionKey) => {
229
+ const chapter = chapters[chapterKey]
230
+ const sectionData = chapter?.sections?.[sectionKey]
231
+ return sectionData?.title || `セクション ${sectionKey}`
232
+ }
233
+
234
+ // 次の章かどうかを判定
235
+ const isNextChapter = (chapterKey) => {
236
+ return props.nextChapter === chapterKey
237
+ }
238
+
239
+ // サイズ設定に基づく動的スタイル
240
+ const sizeConfigs = {
241
+ xs: {
242
+ columns: 4,
243
+ headerPadding: 'pt-6 pb-3 px-4 ',
244
+ headerTitle: 'text-2xl',
245
+ mainPadding: 'px-4 py-4',
246
+ gridGap: 'gap-x-4 gap-y-2',
247
+ chapterNumber: 'text-2xl',
248
+ chapterTitle: 'text-sm font-bold',
249
+ chapterHeader: 'mb-1',
250
+ sectionList: 'ml-8 space-y-0.5',
251
+ sectionText: 'text-xs',
252
+ appendixNumber: 'text-lg',
253
+ nextBadge: 'px-1 py-0.5 text-xs'
254
+ },
255
+ sm: {
256
+ columns: 3,
257
+ headerPadding: 'pt-8 pb-4 px-6',
258
+ headerTitle: 'text-3xl',
259
+ mainPadding: 'px-6 py-6',
260
+ gridGap: 'gap-x-6 gap-y-3',
261
+ chapterNumber: 'text-3xl',
262
+ chapterTitle: 'text-base',
263
+ chapterHeader: 'mb-2',
264
+ sectionList: 'ml-10 space-y-1',
265
+ sectionText: 'text-sm',
266
+ appendixNumber: 'text-2xl',
267
+ nextBadge: 'px-2 py-1 text-xs'
268
+ },
269
+ md: {
270
+ columns: 2,
271
+ headerPadding: 'pt-14 pb-8 px-10',
272
+ headerTitle: 'text-5xl',
273
+ mainPadding: 'px-6 py-40',
274
+ gridGap: 'gap-x-12 gap-y-2',
275
+ chapterNumber: 'text-6xl',
276
+ chapterTitle: 'text-2xl',
277
+ chapterHeader: 'mb-4',
278
+ sectionList: 'ml-20 space-y-2',
279
+ sectionText: 'text-lg',
280
+ appendixNumber: 'text-4xl',
281
+ nextBadge: 'px-3 py-1 text-sm'
282
+ },
283
+ lg: {
284
+ columns: 2,
285
+ headerPadding: 'pt-16 pb-10 px-12',
286
+ headerTitle: 'text-6xl',
287
+ mainPadding: 'px-16 py-16',
288
+ gridGap: 'gap-x-20 gap-y-10',
289
+ chapterNumber: 'text-7xl',
290
+ chapterTitle: 'text-3xl',
291
+ chapterHeader: 'mb-6',
292
+ sectionList: 'ml-24 space-y-3',
293
+ sectionText: 'text-xl',
294
+ appendixNumber: 'text-5xl',
295
+ nextBadge: 'px-4 py-2 text-base'
296
+ },
297
+ xl: {
298
+ columns: 1,
299
+ headerPadding: 'pt-20 pb-12 px-16',
300
+ headerTitle: 'text-8xl',
301
+ mainPadding: 'px-20 py-20',
302
+ gridGap: 'gap-x-24 gap-y-12',
303
+ chapterNumber: 'text-9xl',
304
+ chapterTitle: 'text-5xl',
305
+ chapterHeader: 'mb-8',
306
+ sectionList: 'ml-32 space-y-4',
307
+ sectionText: 'text-3xl',
308
+ appendixNumber: 'text-7xl',
309
+ nextBadge: 'px-5 py-2 text-lg'
310
+ }
311
+ }
312
+
313
+ const currentConfig = computed(() => sizeConfigs[props.size])
314
+
315
+ // 動的クラス
316
+ const headerClasses = computed(() => `text-left ${currentConfig.value.headerPadding}`)
317
+ const headerTitleClasses = computed(() => `${currentConfig.value.headerTitle} font-bold text-white`)
318
+ const mainContentClasses = computed(() => `flex-1 ${currentConfig.value.mainPadding}`)
319
+ const gridClasses = computed(() => `grid grid-cols-${currentConfig.value.columns} ${currentConfig.value.gridGap}`)
320
+ const chapterHeaderClasses = computed(() => `flex items-center ${currentConfig.value.chapterHeader} relative z-5`)
321
+ const chapterNumberClasses = computed(() => `${currentConfig.value.chapterNumber} font-bold text-white mr-2`)
322
+ const chapterTitleClasses = computed(() => `${currentConfig.value.chapterTitle} font-bold text-white`)
323
+ const sectionListClasses = computed(() => currentConfig.value.sectionList)
324
+ const sectionItemClasses = computed(() => 'flex items-start')
325
+ const sectionTextClasses = computed(() => `text-white ${currentConfig.value.sectionText} leading-relaxed`)
326
+ const appendixNumberClasses = computed(() => `${currentConfig.value.appendixNumber} font-bold text-white mr-6 pb-2`)
327
+ const highlightBorderClasses = computed(() => 'absolute -inset-2 border-4 border-yellow-400 rounded-lg opacity-80')
328
+ const nextBadgeClasses = computed(() => `absolute -top-2 -right-2 bg-yellow-400 text-black ${currentConfig.value.nextBadge} rounded-full font-bold z-10`)
329
+ </script>
330
+
331
+ <style>
332
+ /* このコンポーネント専用のスタイル */
333
+ .table-of-contents-container {
334
+ /* ページ全体をカバーする背景 */
335
+ position: absolute !important;
336
+ top: 0 !important;
337
+ left: 50% !important;
338
+ transform: translateX(-50%) !important;
339
+ width: 100% !important;
340
+ height: 100% !important;
341
+ margin: 0 !important;
342
+ box-sizing: border-box !important;
343
+ z-index: 0 !important;
344
+ }
345
+
346
+ /* 背景色を確実に適用 */
347
+ .table-of-contents-container.bg-theme-color {
348
+ background-color: rgba(2, 132, 199, 1) !important;
349
+ }
350
+ </style>