slidev-theme-gtlabo 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.
@@ -0,0 +1,249 @@
1
+ <template>
2
+ <div class="h-full flex flex-col">
3
+ <!-- ヘッダー部分(Headerコンポーネントを使用) -->
4
+ <Header
5
+ :chapter-data="{ title: '参考文献' }"
6
+ chapter="ref"
7
+ />
8
+
9
+ <!-- 参考文献リスト -->
10
+ <div class="flex-1 overflow-y-auto px-4">
11
+ <div
12
+ v-if="citationsList.length > 0"
13
+ class="space-y-6"
14
+ >
15
+ <div
16
+ v-for="(citation, index) in citationsList"
17
+ :key="citation.key"
18
+ class="citation-item border-l-4 border-sky-500 pl-6 py-2"
19
+ >
20
+ <!-- 引用キー -->
21
+ <div class="text-lg font-semibold text-sky-700 mb-2">
22
+ [{{ citation.key }}]
23
+ </div>
24
+
25
+ <!-- 引用情報 -->
26
+ <div class="text-gray-800 leading-relaxed">
27
+ <!-- 著者 -->
28
+ <span
29
+ v-if="citation.data.author"
30
+ class="font-medium"
31
+ >
32
+ {{ citation.data.author }}
33
+ </span>
34
+
35
+ <!-- タイトル -->
36
+ <span
37
+ v-if="citation.data.title"
38
+ class="italic"
39
+ >
40
+ <span v-if="citation.data.author">. </span>
41
+ "{{ citation.data.title }}"
42
+ </span>
43
+
44
+ <!-- 雑誌名 -->
45
+ <span
46
+ v-if="citation.data.journal"
47
+ class="font-medium"
48
+ >
49
+ <span v-if="citation.data.title || citation.data.author">. </span>
50
+ <em>{{ citation.data.journal }}</em>
51
+ </span>
52
+
53
+ <!-- 巻号 -->
54
+ <span v-if="citation.data.volume">
55
+ <span v-if="citation.data.journal">, </span>
56
+ Vol. {{ citation.data.volume }}
57
+ </span>
58
+
59
+ <span v-if="citation.data.number">
60
+ <span v-if="citation.data.volume">, </span>
61
+ <span v-else-if="citation.data.journal">, </span>
62
+ No. {{ citation.data.number }}
63
+ </span>
64
+
65
+ <!-- ページ -->
66
+ <span v-if="citation.data.pages">
67
+ <span v-if="citation.data.volume || citation.data.number">, </span>
68
+ <span v-else-if="citation.data.journal">, </span>
69
+ pp. {{ citation.data.pages }}
70
+ </span>
71
+
72
+ <!-- 出版社 -->
73
+ <span v-if="citation.data.publisher">
74
+ <span v-if="citation.data.journal || citation.data.pages">, </span>
75
+ {{ citation.data.publisher }}
76
+ </span>
77
+
78
+ <!-- 年 -->
79
+ <span
80
+ v-if="citation.data.year"
81
+ class="font-medium"
82
+ >
83
+ <span v-if="citation.data.author || citation.data.title || citation.data.journal || citation.data.publisher">, </span>
84
+ ({{ citation.data.year }})
85
+ </span>
86
+
87
+ <!-- DOI -->
88
+ <div
89
+ v-if="citation.data.doi"
90
+ class="mt-2 text-sm text-blue-600"
91
+ >
92
+ DOI: <a
93
+ :href="`https://doi.org/${citation.data.doi}`"
94
+ target="_blank"
95
+ class="hover:underline"
96
+ >
97
+ {{ citation.data.doi }}
98
+ </a>
99
+ </div>
100
+
101
+ <!-- URL -->
102
+ <div
103
+ v-if="citation.data.url && !citation.data.doi"
104
+ class="mt-2 text-sm text-blue-600"
105
+ >
106
+ URL: <a
107
+ :href="citation.data.url"
108
+ target="_blank"
109
+ class="hover:underline break-all"
110
+ >
111
+ {{ citation.data.url }}
112
+ </a>
113
+ </div>
114
+
115
+ <!-- ISSN -->
116
+ <div
117
+ v-if="citation.data.issn"
118
+ class="mt-1 text-sm text-gray-600"
119
+ >
120
+ ISSN: {{ citation.data.issn }}
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- 参考文献がない場合 -->
127
+ <div
128
+ v-else
129
+ class="text-center text-gray-500 mt-16"
130
+ >
131
+ <div class="text-xl">
132
+ 参考文献が設定されていません
133
+ </div>
134
+ <div class="text-sm mt-2">
135
+ frontmatterの<code class="bg-gray-200 px-2 py-1 rounded">citations</code>セクションに参考文献を追加してください
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <!-- フッター(必要に応じて) -->
141
+ <div class="flex-shrink-0 mt-8 pt-4 border-t border-gray-300 px-4">
142
+ <div class="text-sm text-gray-600 text-center">
143
+ {{ citationsList.length }} 件の参考文献
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </template>
148
+
149
+ <script setup>
150
+ import { computed } from 'vue'
151
+ import { useSlideContext } from '@slidev/client'
152
+ // Headerコンポーネントをimport
153
+ import Header from './Header.vue'
154
+
155
+ // Slidevコンテキストからconfigsにアクセス
156
+ const { $slidev } = useSlideContext()
157
+ const citations = $slidev.configs.citations || {}
158
+
159
+ // プロパティ定義(必要に応じて)
160
+ const props = defineProps({
161
+ // 表示スタイルのカスタマイズ用
162
+ style: {
163
+ type: String,
164
+ default: 'academic', // 'academic', 'ieee', 'apa' など
165
+ },
166
+ // 表示順序
167
+ sortBy: {
168
+ type: String,
169
+ default: 'key', // 'key', 'author', 'year' など
170
+ }
171
+ })
172
+
173
+ // 参考文献リストを生成
174
+ const citationsList = computed(() => {
175
+ if (!citations || typeof citations !== 'object') {
176
+ return []
177
+ }
178
+
179
+ const list = Object.keys(citations).map(key => ({
180
+ key,
181
+ data: citations[key]
182
+ }))
183
+
184
+ // ソート処理
185
+ switch (props.sortBy) {
186
+ case 'author':
187
+ return list.sort((a, b) => {
188
+ const authorA = a.data.author || ''
189
+ const authorB = b.data.author || ''
190
+ return authorA.localeCompare(authorB)
191
+ })
192
+ case 'year':
193
+ return list.sort((a, b) => {
194
+ const yearA = parseInt(a.data.year) || 0
195
+ const yearB = parseInt(b.data.year) || 0
196
+ return yearB - yearA // 降順(新しい順)
197
+ })
198
+ case 'key':
199
+ default:
200
+ return list.sort((a, b) => a.key.localeCompare(b.key))
201
+ }
202
+ })
203
+
204
+ // スタイル別のフォーマット関数(将来的な拡張用)
205
+ const formatCitation = (citation, style = 'academic') => {
206
+ // 現在は academic スタイルのみ実装
207
+ // 将来的にIEEE、APA等のスタイルを追加可能
208
+ return citation
209
+ }
210
+ </script>
211
+
212
+ <style scoped>
213
+ .citation-item {
214
+ transition: all 0.2s ease-in-out;
215
+ }
216
+
217
+ .citation-item:hover {
218
+ background-color: rgba(14, 165, 233, 0.05);
219
+ border-left-width: 6px;
220
+ }
221
+
222
+ /* スクロールバーのスタイリング */
223
+ .overflow-y-auto {
224
+ scrollbar-width: thin;
225
+ scrollbar-color: rgba(14, 165, 233, 0.3) transparent;
226
+ }
227
+
228
+ .overflow-y-auto::-webkit-scrollbar {
229
+ width: 6px;
230
+ }
231
+
232
+ .overflow-y-auto::-webkit-scrollbar-track {
233
+ background: transparent;
234
+ }
235
+
236
+ .overflow-y-auto::-webkit-scrollbar-thumb {
237
+ background-color: rgba(14, 165, 233, 0.3);
238
+ border-radius: 3px;
239
+ }
240
+
241
+ .overflow-y-auto::-webkit-scrollbar-thumb:hover {
242
+ background-color: rgba(14, 165, 233, 0.5);
243
+ }
244
+
245
+ /* コードブロックのスタイル */
246
+ code {
247
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
248
+ }
249
+ </style>
@@ -0,0 +1,354 @@
1
+ <template>
2
+ <div class="flex flex-col px-4 -mx-3 -mt-4 mb-2">
3
+ <div class="flex ">
4
+ <div class="flex flex-col gap-y-2 w-11/12">
5
+ <!-- プログレス部分 -->
6
+ <div class="flex items-center gap-2 overflow-x-auto">
7
+ <div
8
+ v-for="(chapter, index) in displayChapterList"
9
+ :key="chapter.key"
10
+ class="flex flex-col items-center min-w-35 py-1"
11
+ >
12
+ <!-- 章タイトル -->
13
+ <div class="flex flex-1 text-md font-semibold text-gray-800 text-left w-full whitespace-nowrap mb-2">
14
+ {{ chapter.title }}
15
+ </div>
16
+
17
+ <!-- 進捗バー -->
18
+ <div class="flex flex-1 w-full">
19
+ <!-- セクションが複数ある場合 -->
20
+ <div
21
+ v-if="chapter.sections.length > 0"
22
+ class="flex w-full h-1 gap-0.8 rounded-full overflow-hidden"
23
+ >
24
+ <div
25
+ v-for="(section, sectionIndex) in chapter.sections"
26
+ :key="sectionIndex"
27
+ class="flex-1 h-full transition-all duration-200 ease-in-out cursor-help rounded-full"
28
+ :class="{
29
+ 'bg-gray-700': isCompleted(index, sectionIndex),
30
+ 'bg-blue-500': isCurrent(index, sectionIndex),
31
+ 'bg-gray-300': !isCompleted(index, sectionIndex) && !isCurrent(index, sectionIndex)
32
+ }"
33
+ :style="{
34
+ width: getSectionWidth(chapter.sections.length) + '%'
35
+ }"
36
+ :title="getSectionTitle(chapter, sectionIndex)"
37
+ />
38
+ </div>
39
+
40
+ <!-- セクションがない場合(1節のみ) -->
41
+ <div
42
+ v-else
43
+ class="flex w-full h-1 bg-gray-300 rounded-full overflow-hidden"
44
+ >
45
+ <div
46
+ class="flex-1 h-full transition-all duration-200 ease-in-out cursor-help"
47
+ :class="{
48
+ 'bg-gray-700': isCompleted(index, 0),
49
+ 'bg-blue-500': isCurrent(index, 0),
50
+ 'bg-gray-300': !isCompleted(index, 0) && !isCurrent(index, 0)
51
+ }"
52
+ :title="chapter.title"
53
+ />
54
+ </div>
55
+ </div>
56
+
57
+ <!-- 章間の接続線(最後の章以外) -->
58
+ <div
59
+ v-if="index < displayChapterList.length - 1"
60
+ class="text-gray-400 transition-colors duration-200 mx-1"
61
+ :class="{ 'text-gray-700': isChapterCompleted(index) }"
62
+ />
63
+ </div>
64
+ </div>
65
+
66
+ <!-- タイトル部分 -->
67
+ <div class="flex flex-col justify-center items-left">
68
+ <p
69
+ v-if="displayData"
70
+ class="text-4xl font-bold text-slate-800 inline-block py-4 "
71
+ >
72
+ {{ displayData.title }}
73
+ </p>
74
+ <p
75
+ v-else
76
+ class="text-4xl font-bold text-slate-800 inline-block py-4 bg-amber"
77
+ >
78
+ {{ isSection ? 'セクションが見つかりません' : '章が見つかりません' }}
79
+ </p>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- ページカウンター -->
84
+ <div class="flex w-1/12 items-center justify-center text-gray-800">
85
+ <div class="border-3 border-stone-800 rounded-md flex flex-col w-14 divide-y-2 divide-stone-800">
86
+ <div class="flex flex-1 justify-center items-center text-4xl font-bold">
87
+ {{ adjustedCurrentPage }}
88
+ </div>
89
+ <div class="flex flex-1 justify-center items-center text-xl font-semibold max-h-6">
90
+ {{ adjustedTotal }}
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <div class="flex w-4/5 bg-sky-600 h-1.5 rounded-full -mx-6 " />
96
+ </div>
97
+ </template>
98
+
99
+ <script setup>
100
+ import { computed } from 'vue'
101
+ import { useSlideContext } from '@slidev/client'
102
+
103
+ // Slidevコンテキストからconfigsにアクセス
104
+ const { $slidev } = useSlideContext()
105
+ const chapters = $slidev.configs.chapters || {}
106
+
107
+ const props = defineProps({
108
+ // タイトル用のプロパティ
109
+ chapter: {
110
+ type: String,
111
+ required: false
112
+ },
113
+ chapterData: {
114
+ type: Object,
115
+ required: false
116
+ },
117
+ currentSection: {
118
+ type: String,
119
+ required: false
120
+ },
121
+ // プログレス用のプロパティ(タイトル用と共通利用)
122
+ currentChapter: {
123
+ type: String,
124
+ required: false,
125
+ default: null
126
+ }
127
+ })
128
+
129
+ // セクションかどうかを判定
130
+ const isSection = computed(() => {
131
+ return !!props.currentSection
132
+ })
133
+
134
+ // タイトル表示用のデータ
135
+ const displayData = computed(() => {
136
+ // chapterDataが直接渡された場合はそれを使用
137
+ if (props.chapterData) {
138
+ return props.chapterData
139
+ }
140
+
141
+ // currentSectionが指定されている場合、セクションデータを取得
142
+ if (props.currentSection && (props.chapter || props.currentChapter)) {
143
+ const chapterKey = props.chapter || props.currentChapter
144
+ const chapterData = chapters[chapterKey]
145
+ if (chapterData?.sections?.[props.currentSection]) {
146
+ return chapterData.sections[props.currentSection]
147
+ }
148
+ return null
149
+ }
150
+
151
+ // chapterキーが渡された場合は内部データから取得
152
+ const chapterKey = props.chapter || props.currentChapter
153
+ if (chapterKey && chapters[chapterKey]) {
154
+ return chapters[chapterKey]
155
+ }
156
+
157
+ // どちらも渡されなかった場合はnull
158
+ return null
159
+ })
160
+
161
+ // 参考文献ページかどうかを判定
162
+ const isReferencePage = computed(() => {
163
+ const currentChapterKey = props.currentChapter || props.chapter
164
+ return currentChapterKey === 'ref'
165
+ })
166
+
167
+ // 現在のページがappendixかどうかを判定
168
+ const isCurrentPageAppendix = computed(() => {
169
+ const currentChapterKey = props.currentChapter || props.chapter
170
+ return currentChapterKey && currentChapterKey.startsWith('ap')
171
+ })
172
+
173
+ // 章リストを生成(refを除外し、appendixの表示を条件分岐)
174
+ const chapterList = computed(() => {
175
+ if (!chapters || typeof chapters !== 'object') {
176
+ return []
177
+ }
178
+
179
+ return Object.keys(chapters)
180
+ .filter(chapterKey => chapterKey !== 'ref') // refを除外
181
+ .map(chapterKey => {
182
+ const chapter = chapters[chapterKey]
183
+ const sections = chapter?.sections || {}
184
+ const sectionKeys = Object.keys(sections)
185
+
186
+ return {
187
+ key: chapterKey,
188
+ title: chapter?.title || `章 ${chapterKey}`,
189
+ sections: sectionKeys,
190
+ sectionData: sections,
191
+ isAppendix: chapterKey.startsWith('ap')
192
+ }
193
+ })
194
+ })
195
+
196
+ // 表示する章リスト(appendixページかどうかで切り替え)
197
+ const displayChapterList = computed(() => {
198
+ if (isCurrentPageAppendix.value) {
199
+ // appendixページの場合はappendixのみ表示
200
+ return chapterList.value.filter(chapter => chapter.isAppendix)
201
+ } else {
202
+ // 通常ページの場合はappendixを除外
203
+ return chapterList.value.filter(chapter => !chapter.isAppendix)
204
+ }
205
+ })
206
+
207
+ // appendixの開始ページを計算
208
+ const appendixStartPage = computed(() => {
209
+ const totalPages = $slidev.nav.total
210
+ const appendixChapters = chapterList.value.filter(chapter => chapter.isAppendix)
211
+
212
+ console.log('appendixStartPage計算:')
213
+ console.log(' totalPages:', totalPages)
214
+ console.log(' appendixChapters:', appendixChapters)
215
+
216
+ if (appendixChapters.length === 0) {
217
+ console.log(' appendixなし - 結果:', totalPages + 1)
218
+ return totalPages + 1 // appendixがない場合は存在しないページ番号を返す
219
+ }
220
+
221
+ // appendixの実際のセクション数を計算
222
+ const appendixSectionCount = appendixChapters.reduce((total, chapter) => {
223
+ const sectionCount = Math.max(1, chapter.sections.length)
224
+ console.log(` 章${chapter.key}: ${sectionCount}セクション`)
225
+ return total + sectionCount
226
+ }, 0)
227
+
228
+ console.log(' appendixSectionCount:', appendixSectionCount)
229
+ const result = totalPages - appendixSectionCount + 1
230
+ console.log(' appendixあり - 結果:', result)
231
+ return result
232
+ })
233
+
234
+ // 調整されたページ総数(常にappendix前まで、appendixがない場合は全体から表紙を除く)
235
+ const adjustedTotal = computed(() => {
236
+ // chapterList(全体)からappendixを検索
237
+ const appendixChapters = chapterList.value.filter(chapter => chapter.isAppendix)
238
+
239
+ // デバッグ用ログ
240
+ console.log('全chapterList:', chapterList.value)
241
+ console.log('appendixChapters:', appendixChapters)
242
+ console.log('appendixChapters.length:', appendixChapters.length)
243
+ console.log('$slidev.nav.total:', $slidev.nav.total)
244
+ console.log('appendixStartPage.value:', appendixStartPage.value)
245
+
246
+ if (appendixChapters.length === 0) {
247
+ // appendixがない場合は全体のページ数から表紙を除く
248
+ console.log('appendixなし - 計算結果:', $slidev.nav.total - 1)
249
+ return $slidev.nav.total - 1
250
+ } else {
251
+ // appendixがある場合はappendix開始前のページ数から表紙を除く
252
+ console.log('appendixあり - 計算結果:', appendixStartPage.value - 2)
253
+ return appendixStartPage.value - 2
254
+ }
255
+ })
256
+
257
+ // 表紙を除いた現在のページ番号
258
+ const adjustedCurrentPage = computed(() => {
259
+ return Math.max(1, $slidev.nav.currentPage - 1)
260
+ })
261
+
262
+ // 現在の章のインデックスを取得(表示リスト内での位置)
263
+ const currentChapterIndex = computed(() => {
264
+ const currentChapterKey = props.currentChapter || props.chapter
265
+ if (!currentChapterKey) return -1
266
+ return displayChapterList.value.findIndex(chapter => chapter.key === currentChapterKey)
267
+ })
268
+
269
+ // 現在の節のインデックスを取得
270
+ const currentSectionIndex = computed(() => {
271
+ const currentChapterKey = props.currentChapter || props.chapter
272
+ if (!currentChapterKey || !props.currentSection) return -1
273
+ const chapter = displayChapterList.value[currentChapterIndex.value]
274
+ if (!chapter) return -1
275
+ return chapter.sections.findIndex(section => section === props.currentSection)
276
+ })
277
+
278
+ // セクションタイトルを取得(ツールチップ用)
279
+ const getSectionTitle = (chapter, sectionIndex) => {
280
+ if (chapter.sections.length === 0) {
281
+ return chapter.title
282
+ }
283
+ const sectionKey = chapter.sections[sectionIndex]
284
+ const sectionData = chapter.sectionData[sectionKey]
285
+ return sectionData?.title || `セクション ${sectionIndex + 1}`
286
+ }
287
+
288
+ // セクションの幅を計算(%)
289
+ const getSectionWidth = (totalSections) => {
290
+ if (totalSections <= 1) return 100
291
+ return 100 / totalSections
292
+ }
293
+
294
+ // 指定されたセクションが完了しているかチェック
295
+ const isCompleted = (chapterIndex, sectionIndex) => {
296
+ // 参考文献ページの場合はすべて完了状態
297
+ if (isReferencePage.value) {
298
+ return true
299
+ }
300
+
301
+ const currentIdx = currentChapterIndex.value
302
+ const currentSecIdx = currentSectionIndex.value
303
+
304
+ if (currentIdx === -1) return false
305
+
306
+ // 現在の章より前の章はすべて完了
307
+ if (chapterIndex < currentIdx) return true
308
+
309
+ // 現在の章の場合
310
+ if (chapterIndex === currentIdx) {
311
+ // セクションが指定されている場合
312
+ if (currentSecIdx >= 0) {
313
+ return sectionIndex < currentSecIdx
314
+ }
315
+ // セクションが指定されていない場合は何も完了していない
316
+ return false
317
+ }
318
+
319
+ return false
320
+ }
321
+
322
+ // 指定されたセクションが現在の位置かチェック
323
+ const isCurrent = (chapterIndex, sectionIndex) => {
324
+ // 参考文献ページの場合は何も現在位置にしない(すべて完了状態)
325
+ if (isReferencePage.value) {
326
+ return false
327
+ }
328
+
329
+ const currentIdx = currentChapterIndex.value
330
+ const currentSecIdx = currentSectionIndex.value
331
+
332
+ if (currentIdx === -1) return false
333
+ if (chapterIndex !== currentIdx) return false
334
+
335
+ // セクションが指定されている場合
336
+ if (currentSecIdx >= 0) {
337
+ return sectionIndex === currentSecIdx
338
+ }
339
+
340
+ // セクションが指定されていない場合は最初のセクションが現在位置
341
+ return sectionIndex === 0
342
+ }
343
+
344
+ // 章全体が完了しているかチェック
345
+ const isChapterCompleted = (chapterIndex) => {
346
+ // 参考文献ページの場合はすべて完了状態
347
+ if (isReferencePage.value) {
348
+ return true
349
+ }
350
+
351
+ const currentIdx = currentChapterIndex.value
352
+ return currentIdx > chapterIndex
353
+ }
354
+ </script>