slidev-theme-gtlabo 2.1.0 → 2.1.2
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/Citation.vue
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</template>
|
|
9
9
|
|
|
10
10
|
<script setup>
|
|
11
|
-
import { computed, onMounted, onUnmounted
|
|
11
|
+
import { computed, inject, onMounted, onUnmounted } from 'vue'
|
|
12
12
|
import { useSlideContext } from '@slidev/client'
|
|
13
13
|
|
|
14
14
|
const { $slidev, $page } = useSlideContext()
|
|
@@ -32,65 +32,37 @@ const props = defineProps({
|
|
|
32
32
|
}
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
if (!window.
|
|
37
|
-
window.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
pageCitations: new Map(), // pageNumber -> Set of citation ids
|
|
41
|
-
initialized: false,
|
|
42
|
-
listeners: new Set() // 更新通知用リスナー
|
|
35
|
+
// ページごとの引用管理(slide-bottom.vue用)
|
|
36
|
+
if (!window.pageCitations) {
|
|
37
|
+
window.pageCitations = {
|
|
38
|
+
data: new Map(), // pageNumber -> Set of citation ids
|
|
39
|
+
listeners: new Set()
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
// 更新を通知する関数
|
|
47
43
|
const notifyListeners = () => {
|
|
48
|
-
window.
|
|
49
|
-
try {
|
|
50
|
-
listener()
|
|
51
|
-
} catch (e) {
|
|
52
|
-
// リスナーエラーを無視
|
|
53
|
-
}
|
|
44
|
+
window.pageCitations.listeners.forEach(listener => {
|
|
45
|
+
try { listener() } catch (e) { /* ignore */ }
|
|
54
46
|
})
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
// frontmatterの順番でcitation番号を事前に割り当て
|
|
58
|
-
const initializeCitations = () => {
|
|
59
|
-
if (!window.citationManager.initialized) {
|
|
60
|
-
window.citationManager.citationKeys = Object.keys(citations)
|
|
61
|
-
|
|
62
|
-
window.citationManager.citationKeys.forEach((key, index) => {
|
|
63
|
-
if (!window.citationManager.citations.has(key)) {
|
|
64
|
-
const data = citations[key]
|
|
65
|
-
const newCitation = {
|
|
66
|
-
number: index + 1,
|
|
67
|
-
data: data,
|
|
68
|
-
formatted: formatCitation(data)
|
|
69
|
-
}
|
|
70
|
-
window.citationManager.citations.set(key, newCitation)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
window.citationManager.initialized = true
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
49
|
// 引用データを取得
|
|
79
50
|
const citationData = computed(() => {
|
|
80
|
-
return citations[props.id] || null
|
|
51
|
+
return citations.value[props.id] || null
|
|
81
52
|
})
|
|
82
53
|
|
|
83
|
-
//
|
|
54
|
+
// 引用番号を取得(シンプル版:毎回計算)
|
|
84
55
|
const citationNumber = computed(() => {
|
|
85
|
-
|
|
56
|
+
const citationsData = citations.value
|
|
57
|
+
if (!citationsData || !citationData.value) {
|
|
86
58
|
return '?'
|
|
87
59
|
}
|
|
88
60
|
|
|
89
|
-
|
|
61
|
+
const keys = Object.keys(citationsData)
|
|
62
|
+
const index = keys.indexOf(props.id)
|
|
90
63
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return citation.number
|
|
64
|
+
if (index >= 0) {
|
|
65
|
+
return index + 1
|
|
94
66
|
}
|
|
95
67
|
|
|
96
68
|
return '?'
|
|
@@ -101,12 +73,6 @@ const formattedCitation = computed(() => {
|
|
|
101
73
|
if (!citationData.value) {
|
|
102
74
|
return '引用情報が見つかりません'
|
|
103
75
|
}
|
|
104
|
-
|
|
105
|
-
const citation = window.citationManager.citations.get(props.id)
|
|
106
|
-
if (citation && citation.formatted) {
|
|
107
|
-
return citation.formatted
|
|
108
|
-
}
|
|
109
|
-
|
|
110
76
|
return formatCitation(citationData.value)
|
|
111
77
|
})
|
|
112
78
|
|
|
@@ -161,17 +127,16 @@ const formatCitation = (data) => {
|
|
|
161
127
|
return citation || '引用情報が不完全です'
|
|
162
128
|
}
|
|
163
129
|
|
|
164
|
-
//
|
|
130
|
+
// 現在のページに引用を登録(slide-bottom.vue用)
|
|
165
131
|
const registerCitation = () => {
|
|
166
|
-
// $page はこのコンポーネントが配置されているスライドの番号(静的)
|
|
167
132
|
const page = $page
|
|
168
133
|
if (!page) return
|
|
169
134
|
|
|
170
|
-
if (!window.
|
|
171
|
-
window.
|
|
135
|
+
if (!window.pageCitations.data.has(page)) {
|
|
136
|
+
window.pageCitations.data.set(page, new Set())
|
|
172
137
|
}
|
|
173
138
|
|
|
174
|
-
const pageSet = window.
|
|
139
|
+
const pageSet = window.pageCitations.data.get(page)
|
|
175
140
|
if (!pageSet.has(props.id)) {
|
|
176
141
|
pageSet.add(props.id)
|
|
177
142
|
notifyListeners()
|
|
@@ -183,18 +148,17 @@ const unregisterCitation = () => {
|
|
|
183
148
|
const page = $page
|
|
184
149
|
if (!page) return
|
|
185
150
|
|
|
186
|
-
const pageSet = window.
|
|
151
|
+
const pageSet = window.pageCitations.data.get(page)
|
|
187
152
|
if (pageSet) {
|
|
188
153
|
pageSet.delete(props.id)
|
|
189
154
|
if (pageSet.size === 0) {
|
|
190
|
-
window.
|
|
155
|
+
window.pageCitations.data.delete(page)
|
|
191
156
|
}
|
|
192
157
|
notifyListeners()
|
|
193
158
|
}
|
|
194
159
|
}
|
|
195
160
|
|
|
196
161
|
onMounted(() => {
|
|
197
|
-
initializeCitations()
|
|
198
162
|
registerCitation()
|
|
199
163
|
})
|
|
200
164
|
|
|
@@ -147,12 +147,23 @@
|
|
|
147
147
|
</template>
|
|
148
148
|
|
|
149
149
|
<script setup>
|
|
150
|
-
import { computed } from 'vue'
|
|
150
|
+
import { computed, inject } from 'vue'
|
|
151
151
|
import { useSlideContext } from '@slidev/client'
|
|
152
152
|
|
|
153
153
|
// Slidevコンテキストからconfigsにアクセス
|
|
154
154
|
const { $slidev } = useSlideContext()
|
|
155
|
-
|
|
155
|
+
|
|
156
|
+
// 方法1: frontmatterから取得(従来通り)
|
|
157
|
+
const frontmatterCitations = $slidev.configs.citations || {}
|
|
158
|
+
|
|
159
|
+
// 方法2: injectから取得(外部ファイル対応)
|
|
160
|
+
const injectedCitations = inject('citations', {})
|
|
161
|
+
|
|
162
|
+
// 両方をマージ(frontmatter優先)
|
|
163
|
+
const citations = computed(() => ({
|
|
164
|
+
...injectedCitations,
|
|
165
|
+
...frontmatterCitations
|
|
166
|
+
}))
|
|
156
167
|
|
|
157
168
|
// プロパティ定義
|
|
158
169
|
const props = defineProps({
|
|
@@ -177,16 +188,17 @@ const props = defineProps({
|
|
|
177
188
|
|
|
178
189
|
// 参考文献リストを生成(frontmatterの順番を維持)
|
|
179
190
|
const citationsList = computed(() => {
|
|
180
|
-
|
|
191
|
+
const citationsData = citations.value
|
|
192
|
+
if (!citationsData || typeof citationsData !== 'object') {
|
|
181
193
|
return []
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
// frontmatterの記述順でキーを取得
|
|
185
|
-
const originalKeys = Object.keys(
|
|
197
|
+
const originalKeys = Object.keys(citationsData)
|
|
186
198
|
|
|
187
199
|
const list = originalKeys.map((key, index) => ({
|
|
188
200
|
key,
|
|
189
|
-
data:
|
|
201
|
+
data: citationsData[key],
|
|
190
202
|
number: index + 1 // frontmatterの順番に基づく番号
|
|
191
203
|
}))
|
|
192
204
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- components/
|
|
1
|
+
<!-- components/SectionDivider.vue -->
|
|
2
2
|
<template>
|
|
3
3
|
<div class="table-of-contents-container flex flex-col justify-start bg-white overflow-hidden relative">
|
|
4
4
|
<!-- 厳密な背景グリッド - Swiss Styleの基礎 -->
|
|
@@ -160,12 +160,23 @@
|
|
|
160
160
|
</template>
|
|
161
161
|
|
|
162
162
|
<script setup>
|
|
163
|
-
import { computed } from 'vue'
|
|
163
|
+
import { computed, inject } from 'vue'
|
|
164
164
|
import { useSlideContext } from '@slidev/client'
|
|
165
165
|
|
|
166
166
|
// Slidevコンテキストからconfigsにアクセス
|
|
167
167
|
const { $slidev } = useSlideContext()
|
|
168
|
-
|
|
168
|
+
|
|
169
|
+
// 方法1: frontmatterから取得(従来通り)
|
|
170
|
+
const frontmatterChapters = $slidev.configs.chapters || {}
|
|
171
|
+
|
|
172
|
+
// 方法2: injectから取得(外部ファイル対応)
|
|
173
|
+
const injectedChapters = inject('chapters', {})
|
|
174
|
+
|
|
175
|
+
// 両方をマージ(frontmatter優先)
|
|
176
|
+
const chapters = computed(() => ({
|
|
177
|
+
...injectedChapters,
|
|
178
|
+
...frontmatterChapters
|
|
179
|
+
}))
|
|
169
180
|
|
|
170
181
|
const props = defineProps({
|
|
171
182
|
nextChapter: {
|
|
@@ -188,7 +199,8 @@ const props = defineProps({
|
|
|
188
199
|
|
|
189
200
|
// セクションの重複を除去する関数
|
|
190
201
|
const getUniqueSections = (chapterKey) => {
|
|
191
|
-
const
|
|
202
|
+
const chaptersData = chapters.value
|
|
203
|
+
const chapter = chaptersData[chapterKey]
|
|
192
204
|
if (!chapter?.sections) return []
|
|
193
205
|
|
|
194
206
|
const sections = chapter.sections
|
|
@@ -221,14 +233,15 @@ const isAppendixSection = computed(() => {
|
|
|
221
233
|
|
|
222
234
|
// 通常の章リスト(refと付録を除外)
|
|
223
235
|
const regularChapters = computed(() => {
|
|
224
|
-
|
|
236
|
+
const chaptersData = chapters.value
|
|
237
|
+
if (!chaptersData || typeof chaptersData !== 'object') {
|
|
225
238
|
return []
|
|
226
239
|
}
|
|
227
240
|
|
|
228
|
-
return Object.keys(
|
|
241
|
+
return Object.keys(chaptersData)
|
|
229
242
|
.filter(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
|
|
230
243
|
.map(chapterKey => {
|
|
231
|
-
const chapter =
|
|
244
|
+
const chapter = chaptersData[chapterKey]
|
|
232
245
|
const sections = chapter?.sections || {}
|
|
233
246
|
const sectionKeys = Object.keys(sections)
|
|
234
247
|
|
|
@@ -244,14 +257,15 @@ const regularChapters = computed(() => {
|
|
|
244
257
|
|
|
245
258
|
// 付録の章リスト
|
|
246
259
|
const appendixChapters = computed(() => {
|
|
247
|
-
|
|
260
|
+
const chaptersData = chapters.value
|
|
261
|
+
if (!chaptersData || typeof chaptersData !== 'object') {
|
|
248
262
|
return []
|
|
249
263
|
}
|
|
250
264
|
|
|
251
|
-
return Object.keys(
|
|
265
|
+
return Object.keys(chaptersData)
|
|
252
266
|
.filter(chapterKey => chapterKey.startsWith('ap'))
|
|
253
267
|
.map(chapterKey => {
|
|
254
|
-
const chapter =
|
|
268
|
+
const chapter = chaptersData[chapterKey]
|
|
255
269
|
const sections = chapter?.sections || {}
|
|
256
270
|
const sectionKeys = Object.keys(sections)
|
|
257
271
|
|
|
@@ -267,7 +281,8 @@ const appendixChapters = computed(() => {
|
|
|
267
281
|
|
|
268
282
|
// セクションタイトルを取得
|
|
269
283
|
const getSectionTitle = (chapterKey, sectionKey) => {
|
|
270
|
-
const
|
|
284
|
+
const chaptersData = chapters.value
|
|
285
|
+
const chapter = chaptersData[chapterKey]
|
|
271
286
|
const sectionData = chapter?.sections?.[sectionKey]
|
|
272
287
|
return sectionData?.title || `セクション ${sectionKey}`
|
|
273
288
|
}
|
package/package.json
CHANGED
|
@@ -1,93 +0,0 @@
|
|
|
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>
|