slidev-theme-gtlabo 2.0.0 → 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.
- package/components/SectionDivider.vue +200 -128
- package/package.json +1 -1
- package/components/TableOfContents.vue +0 -350
|
@@ -1,72 +1,126 @@
|
|
|
1
|
-
<!-- components/
|
|
1
|
+
<!-- components/TableOfContents.vue -->
|
|
2
2
|
<template>
|
|
3
|
-
<div class="table-of-contents-container flex flex-col justify-
|
|
4
|
-
<!--
|
|
5
|
-
<div class="absolute inset-0 z-0 pointer-events-none opacity-10"
|
|
6
|
-
style="background-image: linear-gradient(#0f172a 1px, transparent 1px), linear-gradient(to right, #0f172a 1px, transparent 1px); background-size: 50px 50px;">
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<!-- 非対称の幾何学的構成要素 - 左上の強調 -->
|
|
10
|
-
<div class="absolute top-0 left-0 w-2 h-32 bg-slate-900 z-5"></div>
|
|
11
|
-
<div class="absolute top-0 left-0 w-32 h-2 bg-slate-900 z-5"></div>
|
|
12
|
-
|
|
13
|
-
<!-- 非対称の幾何学的構成要素 - 右下のアクセント -->
|
|
14
|
-
<div class="absolute bottom-0 right-0 w-48 h-2 bg-slate-900 z-5"></div>
|
|
15
|
-
<div class="absolute bottom-12 right-0 w-1 h-48 bg-slate-900 opacity-30 z-5"></div>
|
|
16
|
-
|
|
17
|
-
<!-- 右側の装飾バー群 - cover.vueと同じスタイル -->
|
|
18
|
-
<div class="absolute right-0 top-0 w-1/4 h-full z-0 pointer-events-none">
|
|
19
|
-
<div class="absolute right-0 top-0 w-full h-full bg-gradient-to-l from-slate-200 to-slate-200/10" />
|
|
20
|
-
<div class="absolute right-[15%] top-0 w-32 h-[35%] bg-sky-600/10" />
|
|
21
|
-
<div class="absolute right-0 bottom-0 w-2 h-[40%] bg-slate-900" />
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- 機能的なsky-500のアクセントブロック -->
|
|
25
|
-
<div class="absolute right-0 top-1/4 w-[12%] h-24 bg-sky-500/30 z-5"></div>
|
|
26
|
-
|
|
27
|
-
<!-- ヘッダー - 付録セクション用 -->
|
|
3
|
+
<div class="table-of-contents-container flex flex-col justify-center bg-theme-color">
|
|
4
|
+
<!-- ヘッダー -->
|
|
28
5
|
<div
|
|
29
6
|
v-if="isAppendixSection"
|
|
30
7
|
:class="headerClasses"
|
|
31
|
-
class="relative z-10"
|
|
32
8
|
>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
9
|
+
<p :class="headerTitleClasses">
|
|
10
|
+
付録
|
|
11
|
+
</p>
|
|
38
12
|
</div>
|
|
39
13
|
|
|
40
|
-
<!-- メインコンテンツ
|
|
41
|
-
<div :class="mainContentClasses"
|
|
14
|
+
<!-- メインコンテンツ -->
|
|
15
|
+
<div :class="mainContentClasses">
|
|
42
16
|
<div class="max-w-full w-full">
|
|
43
|
-
<!--
|
|
44
|
-
<div
|
|
45
|
-
|
|
46
|
-
|
|
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"
|
|
47
74
|
:key="chapter.key"
|
|
48
|
-
|
|
49
|
-
:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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>
|
|
55
115
|
</div>
|
|
56
116
|
</div>
|
|
57
117
|
</div>
|
|
58
|
-
|
|
59
|
-
<!-- タイポグラフィックな署名 - 客観性の表明 -->
|
|
60
|
-
<div class="absolute bottom-4 left-6 text-[10px] font-mono text-slate-800 uppercase tracking-widest z-20">
|
|
61
|
-
CONTENTS
|
|
62
|
-
</div>
|
|
63
118
|
</div>
|
|
64
119
|
</template>
|
|
65
|
-
|
|
120
|
+
|
|
66
121
|
<script setup>
|
|
67
122
|
import { computed } from 'vue'
|
|
68
123
|
import { useSlideContext } from '@slidev/client'
|
|
69
|
-
import ChapterItem from './ChapterItem.vue'
|
|
70
124
|
|
|
71
125
|
// Slidevコンテキストからconfigsにアクセス
|
|
72
126
|
const { $slidev } = useSlideContext()
|
|
@@ -76,7 +130,7 @@ const props = defineProps({
|
|
|
76
130
|
nextChapter: {
|
|
77
131
|
type: String,
|
|
78
132
|
required: true,
|
|
79
|
-
description: '
|
|
133
|
+
description: '次に始まる章のキー'
|
|
80
134
|
},
|
|
81
135
|
currentChapter: {
|
|
82
136
|
type: String,
|
|
@@ -124,14 +178,14 @@ const isAppendixSection = computed(() => {
|
|
|
124
178
|
return props.nextChapter.startsWith('ap')
|
|
125
179
|
})
|
|
126
180
|
|
|
127
|
-
//
|
|
128
|
-
const
|
|
181
|
+
// 通常の章リスト(refと付録を除外)
|
|
182
|
+
const regularChapters = computed(() => {
|
|
129
183
|
if (!chapters || typeof chapters !== 'object') {
|
|
130
184
|
return []
|
|
131
185
|
}
|
|
132
186
|
|
|
133
187
|
return Object.keys(chapters)
|
|
134
|
-
.filter(
|
|
188
|
+
.filter(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
|
|
135
189
|
.map(chapterKey => {
|
|
136
190
|
const chapter = chapters[chapterKey]
|
|
137
191
|
const sections = chapter?.sections || {}
|
|
@@ -142,102 +196,117 @@ const getChapterList = (filterFn) => {
|
|
|
142
196
|
title: chapter?.title || `章 ${chapterKey}`,
|
|
143
197
|
sections: sectionKeys,
|
|
144
198
|
sectionData: sections,
|
|
145
|
-
uniqueSections: getUniqueSections(chapterKey)
|
|
199
|
+
uniqueSections: getUniqueSections(chapterKey) // 重複除去済みセクション
|
|
146
200
|
}
|
|
147
201
|
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 通常の章リスト(refと付録を除外)
|
|
151
|
-
const regularChapters = computed(() =>
|
|
152
|
-
getChapterList(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
|
|
153
|
-
)
|
|
202
|
+
})
|
|
154
203
|
|
|
155
204
|
// 付録の章リスト
|
|
156
|
-
const appendixChapters = computed(() =>
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
})
|
|
159
226
|
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
227
|
+
// セクションタイトルを取得
|
|
228
|
+
const getSectionTitle = (chapterKey, sectionKey) => {
|
|
229
|
+
const chapter = chapters[chapterKey]
|
|
230
|
+
const sectionData = chapter?.sections?.[sectionKey]
|
|
231
|
+
return sectionData?.title || `セクション ${sectionKey}`
|
|
232
|
+
}
|
|
164
233
|
|
|
165
234
|
// 次の章かどうかを判定
|
|
166
235
|
const isNextChapter = (chapterKey) => {
|
|
167
236
|
return props.nextChapter === chapterKey
|
|
168
237
|
}
|
|
169
238
|
|
|
170
|
-
//
|
|
239
|
+
// サイズ設定に基づく動的スタイル
|
|
171
240
|
const sizeConfigs = {
|
|
172
241
|
xs: {
|
|
173
242
|
columns: 4,
|
|
174
|
-
headerPadding: 'pt-
|
|
175
|
-
headerTitle: 'text-
|
|
176
|
-
mainPadding: 'px-
|
|
177
|
-
gridGap: 'gap-x-4 gap-y-
|
|
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',
|
|
178
247
|
chapterNumber: 'text-2xl',
|
|
179
|
-
chapterTitle: 'text-
|
|
180
|
-
chapterHeader: 'mb-
|
|
181
|
-
sectionList: '
|
|
182
|
-
sectionText: 'text-
|
|
183
|
-
appendixNumber: 'text-
|
|
184
|
-
nextBadge: '
|
|
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'
|
|
185
254
|
},
|
|
186
255
|
sm: {
|
|
187
256
|
columns: 3,
|
|
188
|
-
headerPadding: 'pt-
|
|
189
|
-
headerTitle: 'text-
|
|
190
|
-
mainPadding: 'px-
|
|
191
|
-
gridGap: 'gap-x-6 gap-y-
|
|
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',
|
|
192
261
|
chapterNumber: 'text-3xl',
|
|
193
|
-
chapterTitle: 'text-
|
|
194
|
-
chapterHeader: 'mb-2
|
|
195
|
-
sectionList: '
|
|
196
|
-
sectionText: 'text-
|
|
262
|
+
chapterTitle: 'text-base',
|
|
263
|
+
chapterHeader: 'mb-2',
|
|
264
|
+
sectionList: 'ml-10 space-y-1',
|
|
265
|
+
sectionText: 'text-sm',
|
|
197
266
|
appendixNumber: 'text-2xl',
|
|
198
|
-
nextBadge: '
|
|
267
|
+
nextBadge: 'px-2 py-1 text-xs'
|
|
199
268
|
},
|
|
200
269
|
md: {
|
|
201
270
|
columns: 2,
|
|
202
|
-
headerPadding: 'pt-
|
|
203
|
-
headerTitle: 'text-
|
|
204
|
-
mainPadding: 'px-
|
|
205
|
-
gridGap: 'gap-x-12 gap-y-
|
|
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',
|
|
206
275
|
chapterNumber: 'text-6xl',
|
|
207
276
|
chapterTitle: 'text-2xl',
|
|
208
|
-
chapterHeader: 'mb-
|
|
209
|
-
sectionList: '
|
|
277
|
+
chapterHeader: 'mb-4',
|
|
278
|
+
sectionList: 'ml-20 space-y-2',
|
|
210
279
|
sectionText: 'text-lg',
|
|
211
280
|
appendixNumber: 'text-4xl',
|
|
212
|
-
nextBadge: '
|
|
281
|
+
nextBadge: 'px-3 py-1 text-sm'
|
|
213
282
|
},
|
|
214
283
|
lg: {
|
|
215
284
|
columns: 2,
|
|
216
|
-
headerPadding: 'pt-
|
|
217
|
-
headerTitle: 'text-
|
|
218
|
-
mainPadding: 'px-16 py-
|
|
219
|
-
gridGap: 'gap-x-
|
|
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',
|
|
220
289
|
chapterNumber: 'text-7xl',
|
|
221
|
-
chapterTitle: 'text-
|
|
222
|
-
chapterHeader: 'mb-
|
|
223
|
-
sectionList: '
|
|
224
|
-
sectionText: 'text-
|
|
290
|
+
chapterTitle: 'text-3xl',
|
|
291
|
+
chapterHeader: 'mb-6',
|
|
292
|
+
sectionList: 'ml-24 space-y-3',
|
|
293
|
+
sectionText: 'text-xl',
|
|
225
294
|
appendixNumber: 'text-5xl',
|
|
226
|
-
nextBadge: '
|
|
295
|
+
nextBadge: 'px-4 py-2 text-base'
|
|
227
296
|
},
|
|
228
297
|
xl: {
|
|
229
298
|
columns: 1,
|
|
230
|
-
headerPadding: 'pt-
|
|
231
|
-
headerTitle: 'text-
|
|
232
|
-
mainPadding: 'px-20 py-
|
|
233
|
-
gridGap: 'gap-x-
|
|
234
|
-
chapterNumber: 'text-
|
|
235
|
-
chapterTitle: 'text-
|
|
236
|
-
chapterHeader: 'mb-
|
|
237
|
-
sectionList: '
|
|
238
|
-
sectionText: 'text-
|
|
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',
|
|
239
308
|
appendixNumber: 'text-7xl',
|
|
240
|
-
nextBadge: '
|
|
309
|
+
nextBadge: 'px-5 py-2 text-lg'
|
|
241
310
|
}
|
|
242
311
|
}
|
|
243
312
|
|
|
@@ -245,14 +314,24 @@ const currentConfig = computed(() => sizeConfigs[props.size])
|
|
|
245
314
|
|
|
246
315
|
// 動的クラス
|
|
247
316
|
const headerClasses = computed(() => `text-left ${currentConfig.value.headerPadding}`)
|
|
248
|
-
const headerTitleClasses = computed(() => currentConfig.value.headerTitle)
|
|
317
|
+
const headerTitleClasses = computed(() => `${currentConfig.value.headerTitle} font-bold text-white`)
|
|
249
318
|
const mainContentClasses = computed(() => `flex-1 ${currentConfig.value.mainPadding}`)
|
|
250
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`)
|
|
251
329
|
</script>
|
|
252
330
|
|
|
253
|
-
<style
|
|
331
|
+
<style>
|
|
254
332
|
/* このコンポーネント専用のスタイル */
|
|
255
333
|
.table-of-contents-container {
|
|
334
|
+
/* ページ全体をカバーする背景 */
|
|
256
335
|
position: absolute !important;
|
|
257
336
|
top: 0 !important;
|
|
258
337
|
left: 50% !important;
|
|
@@ -264,15 +343,8 @@ const gridClasses = computed(() => `grid grid-cols-${currentConfig.value.columns
|
|
|
264
343
|
z-index: 0 !important;
|
|
265
344
|
}
|
|
266
345
|
|
|
267
|
-
/*
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
-webkit-font-smoothing: antialiased;
|
|
271
|
-
-moz-osx-font-smoothing: grayscale;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* タブラーナンバーの最適化 */
|
|
275
|
-
.tabular-nums {
|
|
276
|
-
font-variant-numeric: tabular-nums;
|
|
346
|
+
/* 背景色を確実に適用 */
|
|
347
|
+
.table-of-contents-container.bg-theme-color {
|
|
348
|
+
background-color: rgba(2, 132, 199, 1) !important;
|
|
277
349
|
}
|
|
278
350
|
</style>
|
package/package.json
CHANGED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
<!-- components/TableOfContents.vue -->
|
|
2
|
-
<template>
|
|
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>
|
|
119
|
-
</template>
|
|
120
|
-
|
|
121
|
-
<script setup>
|
|
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 || {}
|
|
128
|
-
|
|
129
|
-
const props = defineProps({
|
|
130
|
-
nextChapter: {
|
|
131
|
-
type: String,
|
|
132
|
-
required: true,
|
|
133
|
-
description: '次に始まる章のキー'
|
|
134
|
-
},
|
|
135
|
-
currentChapter: {
|
|
136
|
-
type: String,
|
|
137
|
-
required: false,
|
|
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
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 次の章が付録かどうかを判定
|
|
177
|
-
const isAppendixSection = computed(() => {
|
|
178
|
-
return props.nextChapter.startsWith('ap')
|
|
179
|
-
})
|
|
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>
|