slidev-theme-gtlabo 2.1.8 → 2.2.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.
@@ -1,9 +1,15 @@
1
1
  <template>
2
2
  <span>
3
- <!-- インライン引用番号 -->
4
- <sup class="text-blue-600 font-semibold cursor-help" :title="formattedCitation">
5
- [{{ citationNumber }}]
3
+ <sup v-if="citationStyle === 'numbered'"
4
+ class="text-blue-600 font-semibold cursor-help"
5
+ :title="formattedCitation">
6
+ {{ displayText }}
6
7
  </sup>
8
+ <span v-else
9
+ class="text-blue-600 font-semibold cursor-help"
10
+ :title="formattedCitation">
11
+ {{ displayText }}
12
+ </span>
7
13
  </span>
8
14
  </template>
9
15
 
@@ -13,141 +19,91 @@ import { useSlideContext } from '@slidev/client'
13
19
 
14
20
  const { $slidev, $page } = useSlideContext()
15
21
 
16
- // $page を数値として取得するヘルパー
17
22
  const getPageNumber = () => {
18
- if (isRef($page)) {
19
- return $page.value
20
- }
23
+ if (isRef($page)) return $page.value
21
24
  return $page
22
25
  }
23
26
 
24
- // 方法1: frontmatterから取得(従来通り)
25
27
  const frontmatterCitations = $slidev.configs.citations || {}
26
-
27
- // 方法2: injectから取得(外部ファイル対応)
28
28
  const injectedCitations = inject('citations', {})
29
-
30
- // 両方をマージ(frontmatter優先)
31
29
  const citations = computed(() => ({
32
30
  ...injectedCitations,
33
31
  ...frontmatterCitations
34
32
  }))
35
33
 
34
+ // slides.md の frontmatter から citationStyle を取得
35
+ const citationStyle = $slidev.configs.citationStyle || 'numbered'
36
+
36
37
  const props = defineProps({
37
- id: {
38
- type: String,
39
- required: true
40
- }
38
+ id: { type: String, required: true }
41
39
  })
42
40
 
43
- // ページごとの引用管理(slide-bottom.vue用)
44
41
  if (!window.pageCitations) {
45
42
  window.pageCitations = {
46
- data: new Map(), // pageNumber -> Set of citation ids
43
+ data: new Map(),
47
44
  listeners: new Set()
48
45
  }
49
46
  }
50
47
 
51
48
  const notifyListeners = () => {
52
49
  window.pageCitations.listeners.forEach(listener => {
53
- try { listener() } catch (e) { /* ignore */ }
50
+ try { listener() } catch (e) {}
54
51
  })
55
52
  }
56
53
 
57
- // 引用データを取得
58
- const citationData = computed(() => {
59
- return citations.value[props.id] || null
60
- })
54
+ const citationData = computed(() => citations.value[props.id] || null)
61
55
 
62
- // 引用番号を取得(シンプル版:毎回計算)
56
+ // 番号形式: [1]
63
57
  const citationNumber = computed(() => {
64
58
  const citationsData = citations.value
65
- if (!citationsData || !citationData.value) {
66
- return '?'
67
- }
68
-
59
+ if (!citationsData || !citationData.value) return '?'
69
60
  const keys = Object.keys(citationsData)
70
61
  const index = keys.indexOf(props.id)
71
-
72
- if (index >= 0) {
73
- return index + 1
74
- }
75
-
76
- return '?'
62
+ return index >= 0 ? index + 1 : '?'
63
+ })
64
+
65
+ // APA インライン形式: (first_author, year)
66
+ const apaInline = computed(() => {
67
+ if (!citationData.value) return '(?)'
68
+ const firstAuthor = citationData.value.first_author || '?'
69
+ const year = citationData.value.year || '?'
70
+ return `(${firstAuthor}, ${year})`
71
+ })
72
+
73
+ // スタイルに応じて表示テキストを切り替え
74
+ const displayText = computed(() => {
75
+ if (citationStyle === 'apa') return apaInline.value
76
+ return `[${citationNumber.value}]`
77
77
  })
78
78
 
79
- // フォーマットされた引用テキスト
80
79
  const formattedCitation = computed(() => {
81
- if (!citationData.value) {
82
- return '引用情報が見つかりません'
83
- }
80
+ if (!citationData.value) return '引用情報が見つかりません'
84
81
  return formatCitation(citationData.value)
85
82
  })
86
83
 
87
- // 引用をフォーマット
88
84
  const formatCitation = (data) => {
89
- if (!data) {
90
- return '引用情報が見つかりません'
91
- }
92
-
85
+ if (!data) return '引用情報が見つかりません'
93
86
  let citation = ''
94
-
95
- if (data.author) {
96
- citation += data.author
97
- }
98
-
99
- if (data.title) {
100
- citation += citation ? `, "${data.title}"` : `"${data.title}"`
101
- }
102
-
103
- if (data.journal) {
104
- citation += citation ? `, ${data.journal}` : data.journal
105
- }
106
-
107
- if (data.volume && data.number) {
108
- citation += `, Vol. ${data.volume}, No. ${data.number}`
109
- } else if (data.volume) {
110
- citation += `, Vol. ${data.volume}`
111
- } else if (data.number) {
112
- citation += `, No. ${data.number}`
113
- }
114
-
115
- if (data.pages) {
116
- citation += `, pp. ${data.pages}`
117
- }
118
-
119
- if (data.year) {
120
- citation += citation ? ` (${data.year})` : data.year
121
- }
122
-
123
- if (data.publisher) {
124
- citation += citation ? `, ${data.publisher}` : data.publisher
125
- }
126
-
127
- if (data.url) {
128
- citation += citation ? `, ${data.url}` : data.url
129
- }
130
-
131
- if (data.issn) {
132
- citation += `, ISSN: ${data.issn}`
133
- }
134
-
87
+ if (data.author) citation += data.author
88
+ if (data.title) citation += citation ? `, "${data.title}"` : `"${data.title}"`
89
+ if (data.journal) citation += citation ? `, ${data.journal}` : data.journal
90
+ if (data.volume && data.number) citation += `, Vol. ${data.volume}, No. ${data.number}`
91
+ else if (data.volume) citation += `, Vol. ${data.volume}`
92
+ else if (data.number) citation += `, No. ${data.number}`
93
+ if (data.pages) citation += `, pp. ${data.pages}`
94
+ if (data.year) citation += citation ? ` (${data.year})` : data.year
95
+ if (data.publisher) citation += citation ? `, ${data.publisher}` : data.publisher
96
+ if (data.url) citation += citation ? `, ${data.url}` : data.url
97
+ if (data.issn) citation += `, ISSN: ${data.issn}`
135
98
  return citation || '引用情報が不完全です'
136
99
  }
137
100
 
138
- // 現在のページに引用を登録(slide-bottom.vue用)
139
101
  const registerCitation = () => {
140
102
  const page = getPageNumber()
141
-
142
- if (!page) {
143
- console.warn('Citation: page is null or undefined')
144
- return
145
- }
146
-
103
+ if (!page) return
147
104
  if (!window.pageCitations.data.has(page)) {
148
105
  window.pageCitations.data.set(page, new Set())
149
106
  }
150
-
151
107
  const pageSet = window.pageCitations.data.get(page)
152
108
  if (!pageSet.has(props.id)) {
153
109
  pageSet.add(props.id)
@@ -155,28 +111,19 @@ const registerCitation = () => {
155
111
  }
156
112
  }
157
113
 
158
- // 現在のページから引用を解除
159
114
  const unregisterCitation = () => {
160
115
  const page = getPageNumber()
161
116
  if (!page) return
162
-
163
117
  const pageSet = window.pageCitations.data.get(page)
164
118
  if (pageSet) {
165
119
  pageSet.delete(props.id)
166
- if (pageSet.size === 0) {
167
- window.pageCitations.data.delete(page)
168
- }
120
+ if (pageSet.size === 0) window.pageCitations.data.delete(page)
169
121
  notifyListeners()
170
122
  }
171
123
  }
172
124
 
173
- onMounted(() => {
174
- registerCitation()
175
- })
176
-
177
- onUnmounted(() => {
178
- unregisterCitation()
179
- })
125
+ onMounted(() => registerCitation())
126
+ onUnmounted(() => unregisterCitation())
180
127
  </script>
181
128
 
182
129
  <style scoped>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <span class="eq-ref cursor-help" :title="tooltipText">({{ displayNumber }})</span>
3
+ </template>
4
+
5
+ <script setup>
6
+ import { computed, inject } from 'vue'
7
+
8
+ const props = defineProps({
9
+ id: {
10
+ type: String,
11
+ required: true
12
+ }
13
+ })
14
+
15
+ const equationRegistry = inject('equationRegistry', null)
16
+
17
+ const displayNumber = computed(() => {
18
+ if (!equationRegistry) return '?'
19
+ const num = equationRegistry.getNumber(props.id)
20
+ return num ?? '?'
21
+ })
22
+
23
+ const tooltipText = computed(() => {
24
+ return `式 (${displayNumber.value})`
25
+ })
26
+ </script>
27
+
28
+ <style scoped>
29
+ .eq-ref {
30
+ color: #2563eb;
31
+ font-weight: 500;
32
+ }
33
+ </style>
@@ -2,6 +2,7 @@
2
2
  <component
3
3
  :is="containerTag"
4
4
  :class="['math-text-container', containerClass]"
5
+ :style="eq ? 'position: relative; display: block;' : ''"
5
6
  >
6
7
  <!-- スロットが使われている場合 -->
7
8
  <template v-if="hasSlotContent">
@@ -72,11 +73,16 @@
72
73
  </div>
73
74
  </template>
74
75
  </template>
76
+
77
+ <!-- 数式番号 -->
78
+ <span v-if="eq && eqNumber !== null" class="eq-number">
79
+ ({{ eqNumber }})
80
+ </span>
75
81
  </component>
76
82
  </template>
77
83
 
78
84
  <script setup>
79
- import { computed, ref, onMounted, nextTick, watch, useSlots } from 'vue'
85
+ import { computed, ref, onMounted, onUnmounted, nextTick, watch, useSlots, inject } from 'vue'
80
86
 
81
87
  const props = defineProps({
82
88
  text: {
@@ -103,36 +109,42 @@ const props = defineProps({
103
109
  type: String,
104
110
  default: ''
105
111
  },
106
- // シンプルモード(SimpleMathText相当)
107
112
  simple: {
108
113
  type: Boolean,
109
114
  default: false
110
115
  },
111
- // Markdownを無効にする
112
116
  disableMarkdown: {
113
117
  type: Boolean,
114
118
  default: false
115
119
  },
116
- // カスタム区切り文字パターン
117
120
  customDelimiters: {
118
121
  type: Array,
119
122
  default: null
123
+ },
124
+ eq: {
125
+ type: String,
126
+ default: null
120
127
  }
121
128
  })
122
129
 
130
+ // 数式レジストリ
131
+ const equationRegistry = inject('equationRegistry', null)
132
+
133
+ const eqNumber = computed(() => {
134
+ if (!props.eq || !equationRegistry) return null
135
+ return equationRegistry.getNumber(props.eq)
136
+ })
137
+
123
138
  const slots = useSlots()
124
139
  const mathElements = ref({})
125
140
 
126
- // スロットにコンテンツがあるかチェック
127
141
  const hasSlotContent = computed(() => {
128
142
  return slots.default && slots.default().length > 0
129
143
  })
130
144
 
131
- // テキストコンテンツを処理(Markdown + 改行 + HTMLエスケープ)
132
145
  const processTextContent = (content) => {
133
146
  if (!content) return ''
134
147
 
135
- // まずHTMLエスケープ(数式とMarkdownは後で処理)
136
148
  let processed = content
137
149
  .replace(/&/g, '&amp;')
138
150
  .replace(/</g, '&lt;')
@@ -140,56 +152,28 @@ const processTextContent = (content) => {
140
152
  .replace(/"/g, '&quot;')
141
153
  .replace(/'/g, '&#x27;')
142
154
 
143
- // Markdownが有効な場合の処理
144
155
  if (!props.disableMarkdown) {
145
- // リストアイテム(- で始まる行)
146
156
  processed = processed.replace(/^- (.+)$/gm, '<li>$1</li>')
147
-
148
- // 連続するliタグをulで囲む
149
157
  processed = processed.replace(/(<li>.*<\/li>(?:\n<li>.*<\/li>)*)/g, '<ul>$1</ul>')
150
-
151
- // 番号付きリスト(1. で始まる行)
152
158
  processed = processed.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
153
-
154
- // 連続する番号付きliタグをolで囲む(ulの後に処理)
155
159
  processed = processed.replace(/(<li>.*<\/li>(?:\n<li>.*<\/li>)*)/g, (match) => {
156
- // 既にulで囲まれていない場合のみolで囲む
157
- if (!match.includes('<ul>')) {
158
- return '<ol>' + match + '</ol>'
159
- }
160
+ if (!match.includes('<ul>')) return '<ol>' + match + '</ol>'
160
161
  return match
161
162
  })
162
-
163
- // 太字 **text** または __text__
164
163
  processed = processed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
165
164
  processed = processed.replace(/__(.*?)__/g, '<strong>$1</strong>')
166
-
167
- // イタリック *text* または _text_(ただし数式の*は除外)
168
165
  processed = processed.replace(/(?<!\$[^$]*)\*([^*\n]+?)\*(?![^$]*\$)/g, '<em>$1</em>')
169
166
  processed = processed.replace(/(?<!\$[^$]*)_([^_\n]+?)_(?![^$]*\$)/g, '<em>$1</em>')
170
-
171
- // コードスパン `code`
172
167
  processed = processed.replace(/`([^`]+)`/g, '<code>$1</code>')
173
-
174
- // リンク [text](url)
175
168
  processed = processed.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
176
-
177
- // 見出し ### text
178
169
  processed = processed.replace(/^### (.+)$/gm, '<h3>$1</h3>')
179
170
  processed = processed.replace(/^## (.+)$/gm, '<h2>$1</h2>')
180
171
  processed = processed.replace(/^# (.+)$/gm, '<h1>$1</h1>')
181
-
182
- // 水平線 ---
183
172
  processed = processed.replace(/^---$/gm, '<hr>')
184
-
185
- // 引用 > text
186
173
  processed = processed.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
187
174
  }
188
175
 
189
- // 改行を<br>タグに変換(ただし、HTMLタグの直後は除く)
190
176
  processed = processed.replace(/\n(?!<)/g, '<br>')
191
-
192
- // HTMLタグ間の不要な<br>を除去
193
177
  processed = processed.replace(/<\/([^>]+)><br><([^>\/][^>]*)>/g, '</$1><$2>')
194
178
  processed = processed.replace(/<\/li><br>/g, '</li>')
195
179
  processed = processed.replace(/<br><li>/g, '<li>')
@@ -201,7 +185,6 @@ const processTextContent = (content) => {
201
185
  return processed
202
186
  }
203
187
 
204
- // スロットの内容をテキストに変換(改良版)
205
188
  const slotTextContent = computed(() => {
206
189
  if (!hasSlotContent.value) return ''
207
190
 
@@ -209,42 +192,23 @@ const slotTextContent = computed(() => {
209
192
  if (typeof vnode === 'string') return vnode
210
193
  if (typeof vnode === 'number') return String(vnode)
211
194
  if (!vnode) return ''
212
-
213
- if (Array.isArray(vnode)) {
214
- return vnode.map(extractTextFromVNode).join('')
215
- }
216
-
217
- // テキストノードの処理
195
+ if (Array.isArray(vnode)) return vnode.map(extractTextFromVNode).join('')
218
196
  if (vnode.type === 'text' || vnode.type === Text || typeof vnode.children === 'string') {
219
197
  return vnode.children || vnode.text || ''
220
198
  }
221
-
222
- // 改行要素の処理
223
- if (vnode.type === 'br') {
224
- return '\n'
225
- }
226
-
227
- // 子要素がある場合の再帰処理
228
- if (Array.isArray(vnode.children)) {
229
- return vnode.children.map(extractTextFromVNode).join('')
230
- }
231
-
232
- // コンポーネントの場合はプレースホルダーを返す
199
+ if (vnode.type === 'br') return '\n'
200
+ if (Array.isArray(vnode.children)) return vnode.children.map(extractTextFromVNode).join('')
233
201
  if (typeof vnode.type === 'object' || typeof vnode.type === 'function') {
234
202
  return `<COMPONENT:${vnode.type.name || 'Unknown'}>`
235
203
  }
236
-
237
204
  return vnode.children || ''
238
205
  }
239
206
 
240
- const result = slots.default().map(extractTextFromVNode).join('')
241
- return result
207
+ return slots.default().map(extractTextFromVNode).join('')
242
208
  })
243
209
 
244
- // デフォルトの区切り文字パターン
245
210
  const getDelimiters = () => {
246
211
  if (props.simple) {
247
- // シンプルモード:$...$のみ
248
212
  return [
249
213
  {
250
214
  pattern: /\$([^$\n]+)\$/g,
@@ -255,35 +219,30 @@ const getDelimiters = () => {
255
219
  }
256
220
 
257
221
  return props.customDelimiters || [
258
- // LaTeX環境(最優先)
259
222
  {
260
223
  pattern: /\\begin\{(align\*?|equation\*?|gather\*?|multline\*?|split|eqnarray\*?|alignat\*?|flalign\*?)\}([\s\S]*?)\\end\{\1\}/g,
261
224
  type: 'block-math',
262
225
  process: (match) => match[0],
263
226
  priority: 0
264
227
  },
265
- // $$...$$(ブロック数式)
266
228
  {
267
229
  pattern: /\$\$([^$]*(?:\$(?!\$)[^$]*)*)\$\$/g,
268
230
  type: 'block-math',
269
231
  process: (match) => match[1].trim(),
270
232
  priority: 1
271
233
  },
272
- // $...$(インライン数式)
273
234
  {
274
235
  pattern: /\$([^$\n]+)\$/g,
275
236
  type: 'inline-math',
276
237
  process: (match) => match[1].trim(),
277
238
  priority: 2
278
239
  },
279
- // \(...\)(インライン数式)
280
240
  {
281
241
  pattern: /\\\(([^)]+)\\\)/g,
282
242
  type: 'inline-math',
283
243
  process: (match) => match[1].trim(),
284
244
  priority: 3
285
245
  },
286
- // \[...\](ブロック数式)
287
246
  {
288
247
  pattern: /\\\[([^\]]+)\\\]/g,
289
248
  type: 'block-math',
@@ -293,7 +252,6 @@ const getDelimiters = () => {
293
252
  ]
294
253
  }
295
254
 
296
- // テキストを解析してセグメントに分割
297
255
  const parseTextToSegments = (inputText) => {
298
256
  if (!inputText) return []
299
257
 
@@ -302,7 +260,6 @@ const parseTextToSegments = (inputText) => {
302
260
  let currentText = inputText
303
261
  const mathBlocks = []
304
262
 
305
- // 全ての数式パターンを検出
306
263
  delimiters.forEach((delimiter, delimiterIndex) => {
307
264
  let match
308
265
  const regex = new RegExp(delimiter.pattern.source, delimiter.pattern.flags)
@@ -319,13 +276,11 @@ const parseTextToSegments = (inputText) => {
319
276
  }
320
277
  })
321
278
 
322
- // 開始位置でソート、重複する場合は優先度で決定
323
279
  mathBlocks.sort((a, b) => {
324
280
  if (a.start !== b.start) return a.start - b.start
325
281
  return a.priority - b.priority
326
282
  })
327
283
 
328
- // 重複する範囲を除去
329
284
  const filteredBlocks = []
330
285
  for (let i = 0; i < mathBlocks.length; i++) {
331
286
  const current = mathBlocks[i]
@@ -333,7 +288,6 @@ const parseTextToSegments = (inputText) => {
333
288
 
334
289
  for (let j = filteredBlocks.length - 1; j >= 0; j--) {
335
290
  const existing = filteredBlocks[j]
336
-
337
291
  if (!(current.end <= existing.start || current.start >= existing.end)) {
338
292
  if (current.priority < existing.priority ||
339
293
  (current.priority === existing.priority && (current.end - current.start) > (existing.end - existing.start))) {
@@ -345,67 +299,43 @@ const parseTextToSegments = (inputText) => {
345
299
  }
346
300
  }
347
301
 
348
- if (shouldAdd) {
349
- filteredBlocks.push(current)
350
- }
302
+ if (shouldAdd) filteredBlocks.push(current)
351
303
  }
352
304
 
353
305
  filteredBlocks.sort((a, b) => a.start - b.start)
354
306
 
355
- // セグメントを構築
356
307
  let lastIndex = 0
357
308
 
358
309
  filteredBlocks.forEach(block => {
359
- // 数式の前のテキスト部分
360
310
  if (block.start > lastIndex) {
361
311
  const textContent = currentText.slice(lastIndex, block.start)
362
312
  if (textContent) {
363
- // 改行で分割してセグメントを作成
364
313
  const lines = textContent.split('\n')
365
314
  lines.forEach((line, lineIndex) => {
366
- if (line || lineIndex === 0) { // 空行も最初の行なら保持
367
- segments.push({
368
- type: 'text',
369
- content: line
370
- })
315
+ if (line || lineIndex === 0) {
316
+ segments.push({ type: 'text', content: line })
371
317
  }
372
- // 改行を追加(最後の行以外)
373
318
  if (lineIndex < lines.length - 1) {
374
- segments.push({
375
- type: 'text',
376
- content: '\n'
377
- })
319
+ segments.push({ type: 'text', content: '\n' })
378
320
  }
379
321
  })
380
322
  }
381
323
  }
382
324
 
383
- // 数式部分
384
- segments.push({
385
- type: block.type,
386
- content: block.content
387
- })
388
-
325
+ segments.push({ type: block.type, content: block.content })
389
326
  lastIndex = block.end
390
327
  })
391
328
 
392
- // 残りのテキスト
393
329
  if (lastIndex < currentText.length) {
394
330
  const textContent = currentText.slice(lastIndex)
395
331
  if (textContent) {
396
332
  const lines = textContent.split('\n')
397
333
  lines.forEach((line, lineIndex) => {
398
334
  if (line || lineIndex === 0) {
399
- segments.push({
400
- type: 'text',
401
- content: line
402
- })
335
+ segments.push({ type: 'text', content: line })
403
336
  }
404
337
  if (lineIndex < lines.length - 1) {
405
- segments.push({
406
- type: 'text',
407
- content: '\n'
408
- })
338
+ segments.push({ type: 'text', content: '\n' })
409
339
  }
410
340
  })
411
341
  }
@@ -414,26 +344,20 @@ const parseTextToSegments = (inputText) => {
414
344
  return segments
415
345
  }
416
346
 
417
- // textプロパティから処理されたセグメント
418
347
  const processedTextSegments = computed(() => {
419
348
  if (!props.text) return []
420
349
  return parseTextToSegments(props.text)
421
350
  })
422
351
 
423
- // スロットから処理されたセグメント
424
352
  const processedSlotSegments = computed(() => {
425
353
  if (!hasSlotContent.value) return []
426
354
  return parseTextToSegments(slotTextContent.value)
427
355
  })
428
356
 
429
- // 数式要素の参照を設定
430
357
  const setMathElement = (key, el) => {
431
- if (el) {
432
- mathElements.value[key] = el
433
- }
358
+ if (el) mathElements.value[key] = el
434
359
  }
435
360
 
436
- // 数式レンダリング
437
361
  const renderMathElements = async () => {
438
362
  await nextTick()
439
363
 
@@ -454,7 +378,6 @@ const renderMathElements = async () => {
454
378
  katex = katexModule.default || katexModule
455
379
  window.katex = katex
456
380
  } catch (e) {
457
- // KaTeXが利用できない場合のフォールバック
458
381
  if (props.simple) {
459
382
  element.innerHTML = `<span style="font-style: italic; color: #0066cc; background: #f0f8ff; padding: 1px 3px; border-radius: 3px;">${formula}</span>`
460
383
  } else {
@@ -490,20 +413,25 @@ const renderMathElements = async () => {
490
413
  }
491
414
 
492
415
  onMounted(() => {
416
+ if (props.eq && equationRegistry) {
417
+ equationRegistry.register(props.eq)
418
+ }
493
419
  renderMathElements()
494
420
  })
495
421
 
422
+ onUnmounted(() => {
423
+ if (props.eq && equationRegistry) {
424
+ equationRegistry.unregister(props.eq)
425
+ }
426
+ })
427
+
496
428
  watch([() => props.text, hasSlotContent], () => {
497
429
  mathElements.value = {}
498
- nextTick(() => {
499
- renderMathElements()
500
- })
430
+ nextTick(() => renderMathElements())
501
431
  }, { flush: 'post' })
502
432
 
503
433
  watch([processedTextSegments, processedSlotSegments], () => {
504
- nextTick(() => {
505
- renderMathElements()
506
- })
434
+ nextTick(() => renderMathElements())
507
435
  }, { flush: 'post' })
508
436
  </script>
509
437
 
@@ -531,7 +459,6 @@ watch([processedTextSegments, processedSlotSegments], () => {
531
459
  text-align: center;
532
460
  }
533
461
 
534
- /* KaTeX用の基本スタイリング */
535
462
  .inline-math-formula :deep(.katex),
536
463
  .block-math-formula :deep(.katex) {
537
464
  font-size: inherit !important;
@@ -546,7 +473,6 @@ watch([processedTextSegments, processedSlotSegments], () => {
546
473
  text-align: center;
547
474
  }
548
475
 
549
- /* シンプルモード用のフォールバックスタイル */
550
476
  .math-text-container.simple-mode .inline-math-formula {
551
477
  font-style: italic;
552
478
  color: #0066cc;
@@ -554,4 +480,13 @@ watch([processedTextSegments, processedSlotSegments], () => {
554
480
  padding: 1px 3px;
555
481
  border-radius: 3px;
556
482
  }
483
+
484
+ .eq-number {
485
+ position: absolute;
486
+ right: 0;
487
+ top: 50%;
488
+ transform: translateY(-50%);
489
+ color: #374151;
490
+ font-size: 0.9em;
491
+ }
557
492
  </style>
@@ -12,7 +12,9 @@
12
12
  :class="textColorClass"
13
13
  :style="customTextStyle"
14
14
  >
15
- {{ title }}
15
+ <!-- スロットがある場合はスロットを使用、ない場合はtitleプロパティを使用 -->
16
+ <slot v-if="$slots.default" />
17
+ <span v-else>{{ title }}</span>
16
18
  </h2>
17
19
  </div>
18
20
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidev-theme-gtlabo",
3
- "version": "2.1.8",
3
+ "version": "2.2.0",
4
4
  "description": "A Slidev theme for laboratory presentations with customizable components",
5
5
  "author": "mksmkss",
6
6
  "license": "MIT",