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.
- package/LICENSE +21 -0
- package/README.md +298 -0
- package/components/Citation.vue +253 -0
- package/components/CitationListPage.vue +249 -0
- package/components/Header.vue +354 -0
- package/components/MathText.vue +557 -0
- package/components/SectionDivider.vue +24 -0
- package/components/SectionTitle.vue +70 -0
- package/components/SubSectionTitle.vue +67 -0
- package/components/TableOfContents.vue +349 -0
- package/components/TextColorBox.vue +97 -0
- package/layouts/cover.vue +69 -0
- package/layouts/default.vue +12 -0
- package/layouts/intro.vue +7 -0
- package/package.json +65 -0
- package/setup/shiki.ts +11 -0
- package/styles/index.ts +3 -0
- package/styles/layout.css +24 -0
- package/uno.config.ts +13 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center my-2 -ml-4">
|
|
3
|
+
<!-- 左側のバー -->
|
|
4
|
+
<lucide-chevron-right
|
|
5
|
+
class="w-12 h-12 text-sky-700 -mr-1"
|
|
6
|
+
:style="customBarStyle"
|
|
7
|
+
/>
|
|
8
|
+
|
|
9
|
+
<!-- タイトルテキスト -->
|
|
10
|
+
<h2
|
|
11
|
+
class="text-5xl font-extrabold m-0 leading-tight tracking-wide"
|
|
12
|
+
:class="textColorClass"
|
|
13
|
+
:style="customTextStyle"
|
|
14
|
+
>
|
|
15
|
+
{{ title }}
|
|
16
|
+
</h2>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { computed } from 'vue'
|
|
22
|
+
|
|
23
|
+
const props = defineProps<{
|
|
24
|
+
title: string
|
|
25
|
+
color?: string
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
// UnoCSS用のカラークラス
|
|
29
|
+
const barColorClass = computed(() => {
|
|
30
|
+
if (!props.color) return 'bg-sky-700'
|
|
31
|
+
|
|
32
|
+
// UnoCSS/Tailwindのカラー名かチェック
|
|
33
|
+
const colorPattern = /^(red|blue|green|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose|slate|gray|zinc|neutral|stone)(-\d{1,3})?$/
|
|
34
|
+
|
|
35
|
+
if (colorPattern.test(props.color)) {
|
|
36
|
+
return `bg-${props.color}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// カスタムカラーの場合は空文字を返してstyleで適用
|
|
40
|
+
return ''
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const textColorClass = computed(() => {
|
|
44
|
+
if (!props.color) return 'text-gray-900'
|
|
45
|
+
|
|
46
|
+
// UnoCSS/Tailwindのカラー名かチェック
|
|
47
|
+
const colorPattern = /^(red|blue|green|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose|slate|gray|zinc|neutral|stone)(-\d{1,3})?$/
|
|
48
|
+
|
|
49
|
+
if (colorPattern.test(props.color)) {
|
|
50
|
+
return `text-${props.color}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// カスタムカラーの場合は空文字を返してstyleで適用
|
|
54
|
+
return ''
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// カスタムカラー用のスタイル
|
|
58
|
+
const customBarStyle = computed(() => {
|
|
59
|
+
if (!props.color || barColorClass.value) return {}
|
|
60
|
+
return { backgroundColor: props.color }
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const customTextStyle = computed(() => {
|
|
64
|
+
if (!props.color || textColorClass.value) return {}
|
|
65
|
+
return { color: props.color }
|
|
66
|
+
})
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="table-of-contents-container flex flex-col justify-center bg-theme-color">
|
|
3
|
+
<!-- ヘッダー -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="isAppendixSection"
|
|
6
|
+
:class="headerClasses"
|
|
7
|
+
>
|
|
8
|
+
<p :class="headerTitleClasses">
|
|
9
|
+
付録
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- メインコンテンツ -->
|
|
14
|
+
<div :class="mainContentClasses">
|
|
15
|
+
<div class="max-w-full w-full">
|
|
16
|
+
<!-- 通常の目次表示 -->
|
|
17
|
+
<div
|
|
18
|
+
v-if="!isAppendixSection"
|
|
19
|
+
:class="gridClasses"
|
|
20
|
+
>
|
|
21
|
+
<div
|
|
22
|
+
v-for="(chapter, index) in regularChapters"
|
|
23
|
+
:key="chapter.key"
|
|
24
|
+
class="flex flex-col"
|
|
25
|
+
:class="{ 'relative': isNextChapter(chapter.key) }"
|
|
26
|
+
>
|
|
27
|
+
<!-- 次の章の強調表示 -->
|
|
28
|
+
<div
|
|
29
|
+
v-if="isNextChapter(chapter.key)"
|
|
30
|
+
:class="highlightBorderClasses"
|
|
31
|
+
/>
|
|
32
|
+
<div
|
|
33
|
+
v-if="isNextChapter(chapter.key)"
|
|
34
|
+
:class="nextBadgeClasses"
|
|
35
|
+
>
|
|
36
|
+
NEXT
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- 章番号とタイトル -->
|
|
40
|
+
<div :class="chapterHeaderClasses">
|
|
41
|
+
<div :class="chapterNumberClasses">
|
|
42
|
+
{{ String(index + 1).padStart(2, '0') }}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="flex-1 border-b border-white pb-2">
|
|
45
|
+
<span :class="chapterTitleClasses">
|
|
46
|
+
{{ chapter.title }}
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- セクション一覧(重複除去済み) -->
|
|
52
|
+
<div :class="sectionListClasses">
|
|
53
|
+
<div
|
|
54
|
+
v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
|
|
55
|
+
:key="`${chapter.key}-${sectionIndex}`"
|
|
56
|
+
:class="sectionItemClasses"
|
|
57
|
+
>
|
|
58
|
+
<span :class="sectionTextClasses">
|
|
59
|
+
{{ sectionData.title }}
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- 付録の目次表示 -->
|
|
67
|
+
<div
|
|
68
|
+
v-else
|
|
69
|
+
:class="gridClasses"
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
v-for="(chapter, index) in appendixChapters"
|
|
73
|
+
:key="chapter.key"
|
|
74
|
+
class="flex flex-col"
|
|
75
|
+
:class="{ 'relative': isNextChapter(chapter.key) }"
|
|
76
|
+
>
|
|
77
|
+
<!-- 次の章の強調表示 -->
|
|
78
|
+
<div
|
|
79
|
+
v-if="isNextChapter(chapter.key)"
|
|
80
|
+
:class="highlightBorderClasses"
|
|
81
|
+
/>
|
|
82
|
+
<div
|
|
83
|
+
v-if="isNextChapter(chapter.key)"
|
|
84
|
+
:class="nextBadgeClasses"
|
|
85
|
+
>
|
|
86
|
+
NEXT
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- 付録番号とタイトル -->
|
|
90
|
+
<div :class="chapterHeaderClasses">
|
|
91
|
+
<div :class="appendixNumberClasses">
|
|
92
|
+
A{{ index + 1 }}
|
|
93
|
+
</div>
|
|
94
|
+
<div class="flex-1 border-b border-white pb-2">
|
|
95
|
+
<h2 :class="chapterTitleClasses">
|
|
96
|
+
{{ chapter.title }}
|
|
97
|
+
</h2>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- セクション一覧(重複除去済み) -->
|
|
102
|
+
<div :class="sectionListClasses">
|
|
103
|
+
<div
|
|
104
|
+
v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
|
|
105
|
+
:key="`${chapter.key}-${sectionIndex}`"
|
|
106
|
+
:class="sectionItemClasses"
|
|
107
|
+
>
|
|
108
|
+
<span :class="sectionTextClasses">
|
|
109
|
+
{{ sectionData.title }}
|
|
110
|
+
</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<script setup>
|
|
121
|
+
import { computed } from 'vue'
|
|
122
|
+
import { useSlideContext } from '@slidev/client'
|
|
123
|
+
|
|
124
|
+
// Slidevコンテキストからconfigsにアクセス
|
|
125
|
+
const { $slidev } = useSlideContext()
|
|
126
|
+
const chapters = $slidev.configs.chapters || {}
|
|
127
|
+
|
|
128
|
+
const props = defineProps({
|
|
129
|
+
nextChapter: {
|
|
130
|
+
type: String,
|
|
131
|
+
required: true,
|
|
132
|
+
description: '次に始まる章のキー'
|
|
133
|
+
},
|
|
134
|
+
currentChapter: {
|
|
135
|
+
type: String,
|
|
136
|
+
required: false,
|
|
137
|
+
description: '現在の章のキー(進捗表示用)'
|
|
138
|
+
},
|
|
139
|
+
size: {
|
|
140
|
+
type: String,
|
|
141
|
+
default: 'md',
|
|
142
|
+
validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
|
|
143
|
+
description: '目次のサイズ(xs, sm, md, lg, xl)'
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// セクションの重複を除去する関数
|
|
148
|
+
const getUniqueSections = (chapterKey) => {
|
|
149
|
+
const chapter = chapters[chapterKey]
|
|
150
|
+
if (!chapter?.sections) return []
|
|
151
|
+
|
|
152
|
+
const sections = chapter.sections
|
|
153
|
+
const sectionKeys = Object.keys(sections)
|
|
154
|
+
const uniqueSections = []
|
|
155
|
+
let previousTitle = null
|
|
156
|
+
|
|
157
|
+
for (const sectionKey of sectionKeys) {
|
|
158
|
+
const sectionData = sections[sectionKey]
|
|
159
|
+
const currentTitle = sectionData?.title || `セクション ${sectionKey}`
|
|
160
|
+
|
|
161
|
+
// 前のセクションと同じタイトルでない場合のみ追加
|
|
162
|
+
if (currentTitle !== previousTitle) {
|
|
163
|
+
uniqueSections.push({
|
|
164
|
+
key: sectionKey,
|
|
165
|
+
title: currentTitle,
|
|
166
|
+
originalData: sectionData
|
|
167
|
+
})
|
|
168
|
+
previousTitle = currentTitle
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return uniqueSections
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 次の章が付録かどうかを判定
|
|
176
|
+
const isAppendixSection = computed(() => {
|
|
177
|
+
return props.nextChapter.startsWith('ap')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// 通常の章リスト(refと付録を除外)
|
|
181
|
+
const regularChapters = computed(() => {
|
|
182
|
+
if (!chapters || typeof chapters !== 'object') {
|
|
183
|
+
return []
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return Object.keys(chapters)
|
|
187
|
+
.filter(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
|
|
188
|
+
.map(chapterKey => {
|
|
189
|
+
const chapter = chapters[chapterKey]
|
|
190
|
+
const sections = chapter?.sections || {}
|
|
191
|
+
const sectionKeys = Object.keys(sections)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
key: chapterKey,
|
|
195
|
+
title: chapter?.title || `章 ${chapterKey}`,
|
|
196
|
+
sections: sectionKeys,
|
|
197
|
+
sectionData: sections,
|
|
198
|
+
uniqueSections: getUniqueSections(chapterKey) // 重複除去済みセクション
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// 付録の章リスト
|
|
204
|
+
const appendixChapters = computed(() => {
|
|
205
|
+
if (!chapters || typeof chapters !== 'object') {
|
|
206
|
+
return []
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return Object.keys(chapters)
|
|
210
|
+
.filter(chapterKey => chapterKey.startsWith('ap'))
|
|
211
|
+
.map(chapterKey => {
|
|
212
|
+
const chapter = chapters[chapterKey]
|
|
213
|
+
const sections = chapter?.sections || {}
|
|
214
|
+
const sectionKeys = Object.keys(sections)
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
key: chapterKey,
|
|
218
|
+
title: chapter?.title || `付録 ${chapterKey}`,
|
|
219
|
+
sections: sectionKeys,
|
|
220
|
+
sectionData: sections,
|
|
221
|
+
uniqueSections: getUniqueSections(chapterKey) // 重複除去済みセクション
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// セクションタイトルを取得
|
|
227
|
+
const getSectionTitle = (chapterKey, sectionKey) => {
|
|
228
|
+
const chapter = chapters[chapterKey]
|
|
229
|
+
const sectionData = chapter?.sections?.[sectionKey]
|
|
230
|
+
return sectionData?.title || `セクション ${sectionKey}`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 次の章かどうかを判定
|
|
234
|
+
const isNextChapter = (chapterKey) => {
|
|
235
|
+
return props.nextChapter === chapterKey
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// サイズ設定に基づく動的スタイル
|
|
239
|
+
const sizeConfigs = {
|
|
240
|
+
xs: {
|
|
241
|
+
columns: 4,
|
|
242
|
+
headerPadding: 'pt-6 pb-3 px-4 ',
|
|
243
|
+
headerTitle: 'text-2xl',
|
|
244
|
+
mainPadding: 'px-4 py-4',
|
|
245
|
+
gridGap: 'gap-x-4 gap-y-2',
|
|
246
|
+
chapterNumber: 'text-2xl',
|
|
247
|
+
chapterTitle: 'text-sm font-bold',
|
|
248
|
+
chapterHeader: 'mb-1',
|
|
249
|
+
sectionList: 'ml-8 space-y-0.5',
|
|
250
|
+
sectionText: 'text-xs',
|
|
251
|
+
appendixNumber: 'text-lg',
|
|
252
|
+
nextBadge: 'px-1 py-0.5 text-xs'
|
|
253
|
+
},
|
|
254
|
+
sm: {
|
|
255
|
+
columns: 3,
|
|
256
|
+
headerPadding: 'pt-8 pb-4 px-6',
|
|
257
|
+
headerTitle: 'text-3xl',
|
|
258
|
+
mainPadding: 'px-6 py-6',
|
|
259
|
+
gridGap: 'gap-x-6 gap-y-3',
|
|
260
|
+
chapterNumber: 'text-3xl',
|
|
261
|
+
chapterTitle: 'text-base',
|
|
262
|
+
chapterHeader: 'mb-2',
|
|
263
|
+
sectionList: 'ml-10 space-y-1',
|
|
264
|
+
sectionText: 'text-sm',
|
|
265
|
+
appendixNumber: 'text-2xl',
|
|
266
|
+
nextBadge: 'px-2 py-1 text-xs'
|
|
267
|
+
},
|
|
268
|
+
md: {
|
|
269
|
+
columns: 2,
|
|
270
|
+
headerPadding: 'pt-14 pb-8 px-10',
|
|
271
|
+
headerTitle: 'text-5xl',
|
|
272
|
+
mainPadding: 'px-12 py-12',
|
|
273
|
+
gridGap: 'gap-x-16 gap-y-8',
|
|
274
|
+
chapterNumber: 'text-6xl',
|
|
275
|
+
chapterTitle: 'text-2xl',
|
|
276
|
+
chapterHeader: 'mb-4',
|
|
277
|
+
sectionList: 'ml-20 space-y-2',
|
|
278
|
+
sectionText: 'text-lg',
|
|
279
|
+
appendixNumber: 'text-4xl',
|
|
280
|
+
nextBadge: 'px-3 py-1 text-sm'
|
|
281
|
+
},
|
|
282
|
+
lg: {
|
|
283
|
+
columns: 2,
|
|
284
|
+
headerPadding: 'pt-16 pb-10 px-12',
|
|
285
|
+
headerTitle: 'text-6xl',
|
|
286
|
+
mainPadding: 'px-16 py-16',
|
|
287
|
+
gridGap: 'gap-x-20 gap-y-10',
|
|
288
|
+
chapterNumber: 'text-7xl',
|
|
289
|
+
chapterTitle: 'text-3xl',
|
|
290
|
+
chapterHeader: 'mb-6',
|
|
291
|
+
sectionList: 'ml-24 space-y-3',
|
|
292
|
+
sectionText: 'text-xl',
|
|
293
|
+
appendixNumber: 'text-5xl',
|
|
294
|
+
nextBadge: 'px-4 py-2 text-base'
|
|
295
|
+
},
|
|
296
|
+
xl: {
|
|
297
|
+
columns: 1,
|
|
298
|
+
headerPadding: 'pt-20 pb-12 px-16',
|
|
299
|
+
headerTitle: 'text-8xl',
|
|
300
|
+
mainPadding: 'px-20 py-20',
|
|
301
|
+
gridGap: 'gap-x-24 gap-y-12',
|
|
302
|
+
chapterNumber: 'text-9xl',
|
|
303
|
+
chapterTitle: 'text-5xl',
|
|
304
|
+
chapterHeader: 'mb-8',
|
|
305
|
+
sectionList: 'ml-32 space-y-4',
|
|
306
|
+
sectionText: 'text-3xl',
|
|
307
|
+
appendixNumber: 'text-7xl',
|
|
308
|
+
nextBadge: 'px-5 py-2 text-lg'
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const currentConfig = computed(() => sizeConfigs[props.size])
|
|
313
|
+
|
|
314
|
+
// 動的クラス
|
|
315
|
+
const headerClasses = computed(() => `text-left ${currentConfig.value.headerPadding}`)
|
|
316
|
+
const headerTitleClasses = computed(() => `${currentConfig.value.headerTitle} font-bold text-white`)
|
|
317
|
+
const mainContentClasses = computed(() => `flex-1 ${currentConfig.value.mainPadding}`)
|
|
318
|
+
const gridClasses = computed(() => `grid grid-cols-${currentConfig.value.columns} ${currentConfig.value.gridGap}`)
|
|
319
|
+
const chapterHeaderClasses = computed(() => `flex items-center ${currentConfig.value.chapterHeader} relative z-5`)
|
|
320
|
+
const chapterNumberClasses = computed(() => `${currentConfig.value.chapterNumber} font-bold text-white mr-2`)
|
|
321
|
+
const chapterTitleClasses = computed(() => `${currentConfig.value.chapterTitle} font-bold text-white`)
|
|
322
|
+
const sectionListClasses = computed(() => currentConfig.value.sectionList)
|
|
323
|
+
const sectionItemClasses = computed(() => 'flex items-start')
|
|
324
|
+
const sectionTextClasses = computed(() => `text-white ${currentConfig.value.sectionText} leading-relaxed`)
|
|
325
|
+
const appendixNumberClasses = computed(() => `${currentConfig.value.appendixNumber} font-bold text-white mr-6 pb-2`)
|
|
326
|
+
const highlightBorderClasses = computed(() => 'absolute -inset-2 border-4 border-yellow-400 rounded-lg opacity-80')
|
|
327
|
+
const nextBadgeClasses = computed(() => `absolute -top-2 -right-2 bg-yellow-400 text-black ${currentConfig.value.nextBadge} rounded-full font-bold z-10`)
|
|
328
|
+
</script>
|
|
329
|
+
|
|
330
|
+
<style>
|
|
331
|
+
/* このコンポーネント専用のスタイル */
|
|
332
|
+
.table-of-contents-container {
|
|
333
|
+
/* ページ全体をカバーする背景 */
|
|
334
|
+
position: absolute !important;
|
|
335
|
+
top: 0 !important;
|
|
336
|
+
left: 50% !important;
|
|
337
|
+
transform: translateX(-50%) !important;
|
|
338
|
+
width: 100% !important;
|
|
339
|
+
height: 100% !important;
|
|
340
|
+
margin: 0 !important;
|
|
341
|
+
box-sizing: border-box !important;
|
|
342
|
+
z-index: 0 !important;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* 背景色を確実に適用 */
|
|
346
|
+
.table-of-contents-container.bg-theme-color {
|
|
347
|
+
background-color: rgba(2, 132, 199, 1) !important;
|
|
348
|
+
}
|
|
349
|
+
</style>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="['shadow-md rounded-lg overflow-hidden', containerClass]">
|
|
3
|
+
<div class="flex justify-center bg-theme-color/30 text-center font-bold">
|
|
4
|
+
<p
|
|
5
|
+
:class="['p-2 flex justify-center items-center text-xl whitespace-pre-line', titleClass]"
|
|
6
|
+
v-html="sanitizedTitle"
|
|
7
|
+
/>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="py-2 px-4 bg-white text-md leading-relaxed">
|
|
10
|
+
<!-- slot使用時はslotを優先、そうでなければtextプロパティを使用 -->
|
|
11
|
+
<slot>
|
|
12
|
+
<p
|
|
13
|
+
:class="['leading-relaxed whitespace-pre-line', textClass]"
|
|
14
|
+
v-html="sanitizedText"
|
|
15
|
+
/>
|
|
16
|
+
</slot>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { computed } from 'vue'
|
|
23
|
+
|
|
24
|
+
const props = defineProps({
|
|
25
|
+
title: {
|
|
26
|
+
type: String,
|
|
27
|
+
required: true
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: ''
|
|
32
|
+
},
|
|
33
|
+
containerClass: {
|
|
34
|
+
type: [String, Array, Object],
|
|
35
|
+
default: ''
|
|
36
|
+
},
|
|
37
|
+
titleClass: {
|
|
38
|
+
type: [String, Array, Object],
|
|
39
|
+
default: ''
|
|
40
|
+
},
|
|
41
|
+
textClass: {
|
|
42
|
+
type: [String, Array, Object],
|
|
43
|
+
default: ''
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// HTMLタグを含むテキストを安全に処理
|
|
48
|
+
const sanitizedText = computed(() => {
|
|
49
|
+
// 基本的なHTMLタグのみを許可するシンプルなサニタイズ
|
|
50
|
+
const allowedTags = ['span', 'strong', 'em', 'u', 'br']
|
|
51
|
+
const allowedAttributes = ['class', 'style', 'v-mark']
|
|
52
|
+
|
|
53
|
+
// より安全な実装が必要な場合は、DOMPurifyなどのライブラリを使用することを推奨
|
|
54
|
+
return props.text
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// タイトルも同様に処理
|
|
58
|
+
const sanitizedTitle = computed(() => {
|
|
59
|
+
return props.title
|
|
60
|
+
})
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
/* v-markディレクティブ用のスタイル */
|
|
65
|
+
:deep(.v-mark) {
|
|
66
|
+
position: relative;
|
|
67
|
+
display: inline;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:deep(.v-mark.underline) {
|
|
71
|
+
text-decoration: underline;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:deep(.v-mark.underline.orange) {
|
|
75
|
+
text-decoration: underline;
|
|
76
|
+
text-decoration-color: orange;
|
|
77
|
+
text-decoration-thickness: 2px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* その他のマーキングスタイル */
|
|
81
|
+
:deep(.v-mark.highlight) {
|
|
82
|
+
background-color: yellow;
|
|
83
|
+
padding: 0 2px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:deep(.v-mark.highlight.blue) {
|
|
87
|
+
background-color: #bfdbfe;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:deep(.v-mark.highlight.green) {
|
|
91
|
+
background-color: #bbf7d0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:deep(.v-mark.highlight.red) {
|
|
95
|
+
background-color: #fecaca;
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="slidev-layout cover relative overflow-hidden bg-gradient-to-br from-white via-gray-100 to-sky-200">
|
|
3
|
+
<!-- 大きな円形要素(右上) -->
|
|
4
|
+
<div class="absolute -right-32 -top-32 w-96 h-96 bg-sky-300/70 rounded-full" />
|
|
5
|
+
<div class="absolute right-8 top-24 w-64 h-64 bg-sky-400/60 rounded-full" />
|
|
6
|
+
|
|
7
|
+
<!-- 左下の三角形要素 -->
|
|
8
|
+
<div class="absolute left-16 bottom-16 w-0 h-0 border-l-48 border-r-48 border-b-80 border-transparent border-b-sky-400/50" />
|
|
9
|
+
<div class="absolute left-32 bottom-32 w-0 h-0 border-l-32 border-r-32 border-b-56 border-transparent border-b-sky-500/55" />
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
<!-- 右下の斜線要素 -->
|
|
13
|
+
<div class="absolute right-25 bottom-5 transform -translate-y-1/2 rotate-60 space-y-8">
|
|
14
|
+
<div class="w-48 h-3 rounded-md bg-sky-500/70" />
|
|
15
|
+
<div class="w-36 h-3 rounded-md bg-sky-500/70" />
|
|
16
|
+
<div class="w-48 h-3 rounded-md bg-sky-500/70" />
|
|
17
|
+
<div class="w-24 h-3 rounded-md bg-sky-500/70" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- 左上の円形グリッド(より濃く) -->
|
|
21
|
+
<div class="absolute left-16 top-16 grid grid-cols-3 gap-3 opacity-60">
|
|
22
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
23
|
+
<div class="w-8 h-8 border-2 border-sky-700 rounded-full" />
|
|
24
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
25
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
26
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
27
|
+
<div class="w-8 h-8 border-2 border-sky-700 rounded-full" />
|
|
28
|
+
<div class="w-8 h-8 border-2 border-sky-700 rounded-full" />
|
|
29
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
30
|
+
<div class="w-8 h-8 bg-sky-600 rounded-full" />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- メインコンテンツ -->
|
|
34
|
+
<div class="relative z-10 flex flex-col justify-center items-start px-8 py-12">
|
|
35
|
+
<div class="max-w-4xl w-full space-y-6">
|
|
36
|
+
<!-- 会議名 -->
|
|
37
|
+
<div class="text-2xl font-semibold text-slate-700">
|
|
38
|
+
{{ $slidev.configs.meetingName }}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- メインタイトル -->
|
|
42
|
+
<div class="text-6xl font-bold text-slate-900 leading-tight ">
|
|
43
|
+
{{ $slidev.configs.coverTitle.first }}
|
|
44
|
+
<br>
|
|
45
|
+
{{ $slidev.configs.coverTitle.second }}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- 著者情報 -->
|
|
49
|
+
<div class="space-y-2">
|
|
50
|
+
<div class="text-2xl font-semibold text-slate-800">
|
|
51
|
+
{{ $slidev.configs.author.affiliation }}
|
|
52
|
+
</div>
|
|
53
|
+
<div class="text-2xl font-bold text-slate-900">
|
|
54
|
+
{{ $slidev.configs.author.name }}
|
|
55
|
+
</div>
|
|
56
|
+
<!-- 日付 -->
|
|
57
|
+
<div class="text-xl font-semibold text-slate-700">
|
|
58
|
+
{{ $slidev.configs.date }}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- 左下のコピーライト風エリア -->
|
|
65
|
+
<div class="absolute bottom-4 left-8 text-slate-700 text-sm font-medium">
|
|
66
|
+
{{ new Date().getFullYear() }} {{ $slidev.configs.author.affiliation }}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slidev-theme-gtlabo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Slidev theme for laboratory presentations with customizable components",
|
|
5
|
+
"author": "mksmkss",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "index.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"components",
|
|
15
|
+
"layouts",
|
|
16
|
+
"styles",
|
|
17
|
+
"setup",
|
|
18
|
+
"uno.config.ts",
|
|
19
|
+
"index.js"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/mksmkss/slidev-theme-gtlabo.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/mksmkss/slidev-theme-gtlabo",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/mksmkss/slidev-theme-gtlabo/issues"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"slidev-theme",
|
|
31
|
+
"slidev",
|
|
32
|
+
"presentation",
|
|
33
|
+
"laboratory",
|
|
34
|
+
"vue"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "slidev build example.md",
|
|
41
|
+
"dev": "slidev example.md --open",
|
|
42
|
+
"export": "slidev export example.md",
|
|
43
|
+
"screenshot": "slidev export example.md --format png"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@slidev/types": "^51.8.2",
|
|
47
|
+
"vue": "^3.3.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@slidev/cli": "^51.8.2",
|
|
51
|
+
"typescript": "^5.0.0",
|
|
52
|
+
"unocss": "^66.2.3"
|
|
53
|
+
},
|
|
54
|
+
"//": "Learn more: https://sli.dev/guide/write-theme.html",
|
|
55
|
+
"slidev": {
|
|
56
|
+
"colorSchema": "light",
|
|
57
|
+
"defaults": {
|
|
58
|
+
"fonts": {
|
|
59
|
+
"sans": "Nunito Sans",
|
|
60
|
+
"mono": "Fira Code"
|
|
61
|
+
},
|
|
62
|
+
"aspectRatio": "4/3"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/setup/shiki.ts
ADDED
package/styles/index.ts
ADDED