@slidev/client 52.13.0 → 52.14.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/constants.ts +1 -0
- package/package.json +19 -17
- package/pages/presenter.vue +355 -52
- package/setup/shiki.ts +2 -2
package/constants.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "52.
|
|
4
|
+
"version": "52.14.0",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,37 +32,39 @@
|
|
|
32
32
|
"@iconify-json/carbon": "^1.2.18",
|
|
33
33
|
"@iconify-json/ph": "^1.2.2",
|
|
34
34
|
"@iconify-json/svg-spinners": "^1.2.4",
|
|
35
|
-
"@shikijs/engine-javascript": "^
|
|
36
|
-
"@shikijs/monaco": "^
|
|
37
|
-
"@shikijs/vitepress-twoslash": "^
|
|
35
|
+
"@shikijs/engine-javascript": "^4.0.0",
|
|
36
|
+
"@shikijs/monaco": "^4.0.0",
|
|
37
|
+
"@shikijs/vitepress-twoslash": "^4.0.0",
|
|
38
38
|
"@slidev/rough-notation": "^0.1.0",
|
|
39
39
|
"@typescript/ata": "^0.9.8",
|
|
40
|
-
"@unhead/vue": "^2.1.
|
|
41
|
-
"@unocss/
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
40
|
+
"@unhead/vue": "^2.1.9",
|
|
41
|
+
"@unocss/extractor-mdc": "^66.6.2",
|
|
42
|
+
"@unocss/preset-mini": "^66.6.2",
|
|
43
|
+
"@unocss/reset": "^66.6.2",
|
|
44
|
+
"@vueuse/core": "^14.2.1",
|
|
45
|
+
"@vueuse/math": "^14.2.1",
|
|
44
46
|
"@vueuse/motion": "^3.0.3",
|
|
45
47
|
"ansis": "^4.2.0",
|
|
46
48
|
"drauu": "^1.0.0",
|
|
47
49
|
"file-saver": "^2.0.5",
|
|
48
50
|
"floating-vue": "^5.2.2",
|
|
49
51
|
"fuse.js": "^7.1.0",
|
|
50
|
-
"katex": "^0.16.
|
|
52
|
+
"katex": "^0.16.33",
|
|
51
53
|
"lz-string": "^1.5.0",
|
|
52
|
-
"mermaid": "^11.12.
|
|
54
|
+
"mermaid": "^11.12.3",
|
|
53
55
|
"monaco-editor": "^0.55.1",
|
|
54
|
-
"nanotar": "^0.
|
|
56
|
+
"nanotar": "^0.3.0",
|
|
55
57
|
"pptxgenjs": "^4.0.1",
|
|
56
58
|
"recordrtc": "^5.6.2",
|
|
57
|
-
"shiki": "^
|
|
59
|
+
"shiki": "^4.0.0",
|
|
58
60
|
"shiki-magic-move": "^1.2.1",
|
|
59
61
|
"typescript": "^5.9.3",
|
|
60
|
-
"unocss": "^66.6.
|
|
61
|
-
"vue": "^3.5.
|
|
62
|
-
"vue-router": "^
|
|
62
|
+
"unocss": "^66.6.2",
|
|
63
|
+
"vue": "^3.5.29",
|
|
64
|
+
"vue-router": "^5.0.3",
|
|
63
65
|
"yaml": "^2.8.2",
|
|
64
|
-
"@slidev/parser": "52.
|
|
65
|
-
"@slidev/types": "52.
|
|
66
|
+
"@slidev/parser": "52.14.0",
|
|
67
|
+
"@slidev/types": "52.14.0"
|
|
66
68
|
},
|
|
67
69
|
"devDependencies": {
|
|
68
70
|
"vite": "^7.3.1"
|
package/pages/presenter.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useHead } from '@unhead/vue'
|
|
3
|
-
import { useLocalStorage, useMouse, useWindowFocus } from '@vueuse/core'
|
|
3
|
+
import { useEventListener, useLocalStorage, useMediaQuery, useMouse, useWindowFocus } from '@vueuse/core'
|
|
4
4
|
import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
|
|
5
5
|
import { createClicksContextBase } from '../composables/useClicks'
|
|
6
6
|
import { useDrawings } from '../composables/useDrawings'
|
|
@@ -32,6 +32,9 @@ import { sharedState } from '../state/shared'
|
|
|
32
32
|
|
|
33
33
|
const inFocus = useWindowFocus()
|
|
34
34
|
const main = ref<HTMLDivElement>()
|
|
35
|
+
const gridContainer = ref<HTMLDivElement>()
|
|
36
|
+
const noteSection = ref<HTMLDivElement>()
|
|
37
|
+
const bottomSection = ref<HTMLDivElement>()
|
|
35
38
|
|
|
36
39
|
registerShortcuts()
|
|
37
40
|
useSwipeControls(main)
|
|
@@ -83,6 +86,158 @@ watch(
|
|
|
83
86
|
)
|
|
84
87
|
|
|
85
88
|
const mainSlideMode = useLocalStorage<'slides' | 'mirror'>('slidev-presenter-main-slide-mode', 'slides')
|
|
89
|
+
|
|
90
|
+
// Resize state (persisted)
|
|
91
|
+
const notesWidth = useLocalStorage('slidev-presenter-notes-width', 360)
|
|
92
|
+
const notesRowSize = useLocalStorage('slidev-presenter-notes-row-size', 280)
|
|
93
|
+
const bottomSectionHeight = ref(0)
|
|
94
|
+
const isResizingNotes = ref(false)
|
|
95
|
+
const isResizingNotesRow = ref(false)
|
|
96
|
+
const resizeStartX = ref(0)
|
|
97
|
+
const resizeStartWidth = ref(360)
|
|
98
|
+
const resizeStartY = ref(0)
|
|
99
|
+
const resizeStartRowSize = ref(280)
|
|
100
|
+
|
|
101
|
+
const RESIZER_LIMITS = {
|
|
102
|
+
minNotesWidth: 240,
|
|
103
|
+
maxNotesWidth: 720,
|
|
104
|
+
minNotesRowSize: 160,
|
|
105
|
+
maxNotesWidthRatio: 0.7,
|
|
106
|
+
maxNotesRowHeightRatio: 0.75,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const isLayout1Wide = useMediaQuery('(min-aspect-ratio: 1/1)')
|
|
110
|
+
const isLayout1Stacked = useMediaQuery('(max-aspect-ratio: 3/5)')
|
|
111
|
+
const isNotesOnRight = computed(() => presenterLayout.value === 1 && isLayout1Wide.value)
|
|
112
|
+
const isNotesResizable = computed(() => !(presenterLayout.value === 1 && isLayout1Stacked.value))
|
|
113
|
+
const isNotesRowResizable = computed(() =>
|
|
114
|
+
(presenterLayout.value === 1 && !isLayout1Stacked.value) || presenterLayout.value === 2 || presenterLayout.value === 3,
|
|
115
|
+
)
|
|
116
|
+
const isNotesOnBottom = computed(() => presenterLayout.value === 1 && !isLayout1Stacked.value)
|
|
117
|
+
|
|
118
|
+
function clampNotesWidth(width: number) {
|
|
119
|
+
if (!Number.isFinite(width))
|
|
120
|
+
return RESIZER_LIMITS.minNotesWidth
|
|
121
|
+
return Math.max(
|
|
122
|
+
RESIZER_LIMITS.minNotesWidth,
|
|
123
|
+
Math.min(RESIZER_LIMITS.maxNotesWidth, Math.round(width)),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function updateNotesWidthFromPointer(clientX: number) {
|
|
128
|
+
const container = gridContainer.value
|
|
129
|
+
if (!container)
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
const rect = container.getBoundingClientRect()
|
|
133
|
+
const deltaX = clientX - resizeStartX.value
|
|
134
|
+
const proposedWidth = isNotesOnRight.value
|
|
135
|
+
? resizeStartWidth.value - deltaX
|
|
136
|
+
: resizeStartWidth.value + deltaX
|
|
137
|
+
const nextWidth = clampNotesWidth(proposedWidth)
|
|
138
|
+
const maxByViewport = Math.round(rect.width * RESIZER_LIMITS.maxNotesWidthRatio)
|
|
139
|
+
notesWidth.value = Math.min(nextWidth, Math.max(RESIZER_LIMITS.minNotesWidth, maxByViewport))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function onNotesResizeStart(e: PointerEvent) {
|
|
143
|
+
if (!isNotesResizable.value)
|
|
144
|
+
return
|
|
145
|
+
if (e.button !== 0)
|
|
146
|
+
return
|
|
147
|
+
e.preventDefault()
|
|
148
|
+
resizeStartX.value = e.clientX
|
|
149
|
+
resizeStartWidth.value = notesWidth.value
|
|
150
|
+
isResizingNotes.value = true
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function clampNotesRowSize(size: number) {
|
|
154
|
+
if (!Number.isFinite(size))
|
|
155
|
+
return RESIZER_LIMITS.minNotesRowSize
|
|
156
|
+
return Math.max(RESIZER_LIMITS.minNotesRowSize, Math.round(size))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function updateNotesRowSizeFromPointer(clientY: number) {
|
|
160
|
+
const container = gridContainer.value
|
|
161
|
+
if (!container)
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
const rect = container.getBoundingClientRect()
|
|
165
|
+
const deltaY = clientY - resizeStartY.value
|
|
166
|
+
const proposed = isNotesOnBottom.value
|
|
167
|
+
? resizeStartRowSize.value - deltaY
|
|
168
|
+
: resizeStartRowSize.value + deltaY
|
|
169
|
+
const maxByViewport = Math.round(rect.height * RESIZER_LIMITS.maxNotesRowHeightRatio)
|
|
170
|
+
notesRowSize.value = Math.min(clampNotesRowSize(proposed), Math.max(RESIZER_LIMITS.minNotesRowSize, maxByViewport))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function onNotesRowResizeStart(e: PointerEvent) {
|
|
174
|
+
if (!isNotesRowResizable.value)
|
|
175
|
+
return
|
|
176
|
+
if (e.button !== 0)
|
|
177
|
+
return
|
|
178
|
+
e.preventDefault()
|
|
179
|
+
|
|
180
|
+
// In layout 2, notesRowSize controls the top section (main slide) height
|
|
181
|
+
// In other layouts, it represents the notes area height
|
|
182
|
+
const currentHeight = presenterLayout.value === 2
|
|
183
|
+
? main.value?.getBoundingClientRect().height
|
|
184
|
+
: noteSection.value?.getBoundingClientRect().height
|
|
185
|
+
resizeStartY.value = e.clientY
|
|
186
|
+
resizeStartRowSize.value = clampNotesRowSize(currentHeight ?? notesRowSize.value)
|
|
187
|
+
isResizingNotesRow.value = true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function updateBottomSectionHeight() {
|
|
191
|
+
const element = bottomSection.value
|
|
192
|
+
if (!element)
|
|
193
|
+
return
|
|
194
|
+
bottomSectionHeight.value = Math.round(element.getBoundingClientRect().height)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function stopResizing() {
|
|
198
|
+
isResizingNotes.value = false
|
|
199
|
+
isResizingNotesRow.value = false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function syncResizerLayoutState() {
|
|
203
|
+
updateBottomSectionHeight()
|
|
204
|
+
normalizeResizerState()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
useEventListener(window, 'pointermove', (e) => {
|
|
208
|
+
if (isResizingNotes.value)
|
|
209
|
+
updateNotesWidthFromPointer(e.clientX)
|
|
210
|
+
if (isResizingNotesRow.value)
|
|
211
|
+
updateNotesRowSizeFromPointer(e.clientY)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
useEventListener(window, 'pointerup', stopResizing)
|
|
215
|
+
useEventListener(window, 'pointercancel', stopResizing)
|
|
216
|
+
|
|
217
|
+
onMounted(() => {
|
|
218
|
+
syncResizerLayoutState()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
useEventListener(window, 'resize', () => {
|
|
222
|
+
syncResizerLayoutState()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
function normalizeResizerState() {
|
|
226
|
+
notesWidth.value = clampNotesWidth(notesWidth.value)
|
|
227
|
+
notesRowSize.value = clampNotesRowSize(notesRowSize.value)
|
|
228
|
+
|
|
229
|
+
const container = gridContainer.value
|
|
230
|
+
if (!container)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
const rect = container.getBoundingClientRect()
|
|
234
|
+
const maxWidth = Math.round(rect.width * RESIZER_LIMITS.maxNotesWidthRatio)
|
|
235
|
+
const maxRowSize = Math.round(rect.height * RESIZER_LIMITS.maxNotesRowHeightRatio)
|
|
236
|
+
|
|
237
|
+
notesWidth.value = Math.min(notesWidth.value, Math.max(RESIZER_LIMITS.minNotesWidth, maxWidth))
|
|
238
|
+
notesRowSize.value = Math.min(notesRowSize.value, Math.max(RESIZER_LIMITS.minNotesRowSize, maxRowSize))
|
|
239
|
+
}
|
|
240
|
+
|
|
86
241
|
const SideEditor = shallowRef<any>()
|
|
87
242
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
88
243
|
import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
|
|
@@ -120,7 +275,34 @@ onMounted(() => {
|
|
|
120
275
|
<CurrentProgressBar />
|
|
121
276
|
<TimerBar />
|
|
122
277
|
</div>
|
|
123
|
-
<div
|
|
278
|
+
<div
|
|
279
|
+
ref="gridContainer"
|
|
280
|
+
class="grid-container"
|
|
281
|
+
:class="`layout${presenterLayout}`"
|
|
282
|
+
:style="{
|
|
283
|
+
'--slidev-presenter-notes-width': `${notesWidth}px`,
|
|
284
|
+
'--slidev-presenter-notes-row-size': `${notesRowSize}px`,
|
|
285
|
+
'--slidev-presenter-bottom-height': `${bottomSectionHeight}px`,
|
|
286
|
+
}"
|
|
287
|
+
>
|
|
288
|
+
<!-- Unified vertical resizer for wide layout -->
|
|
289
|
+
<div
|
|
290
|
+
v-if="isNotesResizable && isNotesOnRight"
|
|
291
|
+
class="notes-vertical-resizer"
|
|
292
|
+
role="separator"
|
|
293
|
+
aria-orientation="vertical"
|
|
294
|
+
title="Resize notes panel"
|
|
295
|
+
@pointerdown="onNotesResizeStart"
|
|
296
|
+
/>
|
|
297
|
+
<!-- Unified vertical resizer for layout 3 -->
|
|
298
|
+
<div
|
|
299
|
+
v-if="isNotesResizable && presenterLayout === 3"
|
|
300
|
+
class="notes-vertical-resizer-left"
|
|
301
|
+
role="separator"
|
|
302
|
+
aria-orientation="vertical"
|
|
303
|
+
title="Resize notes panel"
|
|
304
|
+
@pointerdown="onNotesResizeStart"
|
|
305
|
+
/>
|
|
124
306
|
<div ref="main" class="relative grid-section main flex flex-col">
|
|
125
307
|
<div flex="~ gap-4 items-center" border="b main" p1>
|
|
126
308
|
<span op50 px2>Current</span>
|
|
@@ -155,6 +337,14 @@ onMounted(() => {
|
|
|
155
337
|
/>
|
|
156
338
|
</div>
|
|
157
339
|
<div class="relative grid-section next flex flex-col p-2 lg:p-4">
|
|
340
|
+
<div
|
|
341
|
+
v-if="isNotesRowResizable && presenterLayout === 2"
|
|
342
|
+
class="notes-row-resizer top-[-6px]"
|
|
343
|
+
role="separator"
|
|
344
|
+
aria-orientation="horizontal"
|
|
345
|
+
title="Resize notes panel height"
|
|
346
|
+
@pointerdown="onNotesRowResizeStart"
|
|
347
|
+
/>
|
|
158
348
|
<SlideContainer v-if="nextFrame && nextFrameClicksCtx" key="next">
|
|
159
349
|
<SlideWrapper
|
|
160
350
|
:key="nextFrame[0].no"
|
|
@@ -172,45 +362,64 @@ onMounted(() => {
|
|
|
172
362
|
Next
|
|
173
363
|
</div>
|
|
174
364
|
</div>
|
|
175
|
-
<div
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
:no="currentSlideNo"
|
|
184
|
-
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
185
|
-
:clicks-context="clicksContext"
|
|
186
|
-
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
365
|
+
<div ref="noteSection" class="relative grid-section note overflow-hidden">
|
|
366
|
+
<div
|
|
367
|
+
v-if="isNotesResizable && !isNotesOnRight && presenterLayout !== 3"
|
|
368
|
+
class="notes-resizer right-[-6px]"
|
|
369
|
+
role="separator"
|
|
370
|
+
aria-orientation="vertical"
|
|
371
|
+
title="Resize notes panel"
|
|
372
|
+
@pointerdown="onNotesResizeStart"
|
|
187
373
|
/>
|
|
188
|
-
<
|
|
189
|
-
v-
|
|
190
|
-
|
|
191
|
-
:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
374
|
+
<div
|
|
375
|
+
v-if="isNotesRowResizable && presenterLayout !== 2"
|
|
376
|
+
class="notes-row-resizer"
|
|
377
|
+
:class="isNotesOnBottom ? 'top-[-6px]' : 'bottom-[-6px]'"
|
|
378
|
+
role="separator"
|
|
379
|
+
aria-orientation="horizontal"
|
|
380
|
+
title="Resize notes panel height"
|
|
381
|
+
@pointerdown="onNotesRowResizeStart"
|
|
195
382
|
/>
|
|
196
|
-
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<IconButton title="Decrease font size" @click="decreasePresenterFontSize">
|
|
202
|
-
<div class="i-carbon:zoom-out" />
|
|
203
|
-
</IconButton>
|
|
204
|
-
<IconButton
|
|
383
|
+
|
|
384
|
+
<SideEditor v-if="SideEditor && showEditor" class="h-full" />
|
|
385
|
+
|
|
386
|
+
<div v-else class="h-full grid grid-rows-[1fr_min-content]">
|
|
387
|
+
<NoteEditable
|
|
205
388
|
v-if="__DEV__"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
389
|
+
:key="`edit-${currentSlideNo}`"
|
|
390
|
+
v-model:editing="notesEditing"
|
|
391
|
+
:no="currentSlideNo"
|
|
392
|
+
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
393
|
+
:clicks-context="clicksContext"
|
|
394
|
+
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
395
|
+
/>
|
|
396
|
+
<NoteStatic
|
|
397
|
+
v-else
|
|
398
|
+
:key="`static-${currentSlideNo}`"
|
|
399
|
+
:no="currentSlideNo"
|
|
400
|
+
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
401
|
+
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
402
|
+
:clicks-context="clicksContext"
|
|
403
|
+
/>
|
|
404
|
+
<div border-t border-main />
|
|
405
|
+
<div class="py-1 px-2 text-sm transition" :class="inFocus ? '' : 'op25'">
|
|
406
|
+
<IconButton title="Increase font size" @click="increasePresenterFontSize">
|
|
407
|
+
<div class="i-carbon:zoom-in" />
|
|
408
|
+
</IconButton>
|
|
409
|
+
<IconButton title="Decrease font size" @click="decreasePresenterFontSize">
|
|
410
|
+
<div class="i-carbon:zoom-out" />
|
|
411
|
+
</IconButton>
|
|
412
|
+
<IconButton
|
|
413
|
+
v-if="__DEV__"
|
|
414
|
+
title="Edit Notes"
|
|
415
|
+
@click="notesEditing = !notesEditing"
|
|
416
|
+
>
|
|
417
|
+
<div class="i-carbon:edit" />
|
|
418
|
+
</IconButton>
|
|
419
|
+
</div>
|
|
211
420
|
</div>
|
|
212
421
|
</div>
|
|
213
|
-
<div class="grid-section bottom flex">
|
|
422
|
+
<div ref="bottomSection" class="grid-section bottom flex">
|
|
214
423
|
<NavControls :persist="true" class="transition" :class="inFocus ? '' : 'op25'" />
|
|
215
424
|
<div flex-auto />
|
|
216
425
|
<TimerInlined />
|
|
@@ -229,14 +438,16 @@ onMounted(() => {
|
|
|
229
438
|
}
|
|
230
439
|
|
|
231
440
|
.grid-container {
|
|
441
|
+
--slidev-presenter-notes-width: 360px;
|
|
442
|
+
--slidev-presenter-notes-row-size: 280px;
|
|
232
443
|
--uno: bg-gray/20 flex-1 of-hidden;
|
|
233
444
|
display: grid;
|
|
234
445
|
gap: 1px 1px;
|
|
235
446
|
}
|
|
236
447
|
|
|
237
448
|
.grid-container.layout1 {
|
|
238
|
-
grid-template-columns:
|
|
239
|
-
grid-template-rows: 2fr
|
|
449
|
+
grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);
|
|
450
|
+
grid-template-rows: minmax(0, 2fr) minmax(0, var(--slidev-presenter-notes-row-size)) min-content;
|
|
240
451
|
grid-template-areas:
|
|
241
452
|
'main main'
|
|
242
453
|
'note next'
|
|
@@ -244,27 +455,18 @@ onMounted(() => {
|
|
|
244
455
|
}
|
|
245
456
|
|
|
246
457
|
.grid-container.layout2 {
|
|
247
|
-
grid-template-columns:
|
|
248
|
-
grid-template-rows:
|
|
458
|
+
grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);
|
|
459
|
+
grid-template-rows: minmax(0, var(--slidev-presenter-notes-row-size)) minmax(0, 1fr) min-content;
|
|
249
460
|
grid-template-areas:
|
|
250
461
|
'note main'
|
|
251
462
|
'note next'
|
|
252
463
|
'bottom bottom';
|
|
253
464
|
}
|
|
254
465
|
|
|
255
|
-
.grid-container.layout3 {
|
|
256
|
-
grid-template-columns: 2fr 3fr;
|
|
257
|
-
grid-template-rows: 1fr 1fr min-content;
|
|
258
|
-
grid-template-areas:
|
|
259
|
-
'note next'
|
|
260
|
-
'main next'
|
|
261
|
-
'bottom bottom';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
466
|
@media (max-aspect-ratio: 3/5) {
|
|
265
467
|
.grid-container.layout1 {
|
|
266
468
|
grid-template-columns: 1fr;
|
|
267
|
-
grid-template-rows:
|
|
469
|
+
grid-template-rows: 2fr 1fr 1fr min-content;
|
|
268
470
|
grid-template-areas:
|
|
269
471
|
'main'
|
|
270
472
|
'note'
|
|
@@ -275,8 +477,8 @@ onMounted(() => {
|
|
|
275
477
|
|
|
276
478
|
@media (min-aspect-ratio: 1/1) {
|
|
277
479
|
.grid-container.layout1 {
|
|
278
|
-
grid-template-columns: 1fr 1.1fr
|
|
279
|
-
grid-template-rows: 1fr
|
|
480
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) var(--slidev-presenter-notes-width);
|
|
481
|
+
grid-template-rows: minmax(0, 1fr) minmax(0, var(--slidev-presenter-notes-row-size)) min-content;
|
|
280
482
|
grid-template-areas:
|
|
281
483
|
'main main next'
|
|
282
484
|
'main main note'
|
|
@@ -284,6 +486,15 @@ onMounted(() => {
|
|
|
284
486
|
}
|
|
285
487
|
}
|
|
286
488
|
|
|
489
|
+
.grid-container.layout3 {
|
|
490
|
+
grid-template-columns: var(--slidev-presenter-notes-width) minmax(0, 1fr);
|
|
491
|
+
grid-template-rows: minmax(0, var(--slidev-presenter-notes-row-size)) minmax(0, 1fr) min-content;
|
|
492
|
+
grid-template-areas:
|
|
493
|
+
'note next'
|
|
494
|
+
'main next'
|
|
495
|
+
'bottom bottom';
|
|
496
|
+
}
|
|
497
|
+
|
|
287
498
|
.grid-section {
|
|
288
499
|
--uno: bg-main;
|
|
289
500
|
}
|
|
@@ -302,4 +513,96 @@ onMounted(() => {
|
|
|
302
513
|
.grid-section.bottom {
|
|
303
514
|
grid-area: bottom;
|
|
304
515
|
}
|
|
516
|
+
|
|
517
|
+
.notes-resizer {
|
|
518
|
+
position: absolute;
|
|
519
|
+
top: 0;
|
|
520
|
+
width: 12px;
|
|
521
|
+
height: 100%;
|
|
522
|
+
cursor: col-resize;
|
|
523
|
+
z-index: 10;
|
|
524
|
+
touch-action: none;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.notes-resizer::before {
|
|
528
|
+
content: '';
|
|
529
|
+
position: absolute;
|
|
530
|
+
left: 50%;
|
|
531
|
+
top: 0;
|
|
532
|
+
width: 1px;
|
|
533
|
+
height: 100%;
|
|
534
|
+
background-color: currentColor;
|
|
535
|
+
opacity: 0.2;
|
|
536
|
+
transform: translateX(-50%);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.notes-row-resizer {
|
|
540
|
+
position: absolute;
|
|
541
|
+
left: 0;
|
|
542
|
+
width: 100%;
|
|
543
|
+
height: 12px;
|
|
544
|
+
cursor: row-resize;
|
|
545
|
+
z-index: 10;
|
|
546
|
+
touch-action: none;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.notes-row-resizer::before {
|
|
550
|
+
content: '';
|
|
551
|
+
position: absolute;
|
|
552
|
+
left: 0;
|
|
553
|
+
top: 50%;
|
|
554
|
+
width: 100%;
|
|
555
|
+
height: 1px;
|
|
556
|
+
background-color: currentColor;
|
|
557
|
+
opacity: 0.2;
|
|
558
|
+
transform: translateY(-50%);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.notes-vertical-resizer {
|
|
562
|
+
position: absolute;
|
|
563
|
+
right: var(--slidev-presenter-notes-width);
|
|
564
|
+
top: 0;
|
|
565
|
+
bottom: var(--slidev-presenter-bottom-height, 0px);
|
|
566
|
+
width: 12px;
|
|
567
|
+
cursor: col-resize;
|
|
568
|
+
z-index: 10;
|
|
569
|
+
touch-action: none;
|
|
570
|
+
transform: translateX(50%);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.notes-vertical-resizer::before {
|
|
574
|
+
content: '';
|
|
575
|
+
position: absolute;
|
|
576
|
+
left: 50%;
|
|
577
|
+
top: 0;
|
|
578
|
+
width: 1px;
|
|
579
|
+
height: 100%;
|
|
580
|
+
background-color: currentColor;
|
|
581
|
+
opacity: 0.2;
|
|
582
|
+
transform: translateX(-50%);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.notes-vertical-resizer-left {
|
|
586
|
+
position: absolute;
|
|
587
|
+
left: var(--slidev-presenter-notes-width);
|
|
588
|
+
top: 0;
|
|
589
|
+
bottom: var(--slidev-presenter-bottom-height, 0px);
|
|
590
|
+
width: 12px;
|
|
591
|
+
cursor: col-resize;
|
|
592
|
+
z-index: 10;
|
|
593
|
+
touch-action: none;
|
|
594
|
+
transform: translateX(-50%);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.notes-vertical-resizer-left::before {
|
|
598
|
+
content: '';
|
|
599
|
+
position: absolute;
|
|
600
|
+
left: 50%;
|
|
601
|
+
top: 0;
|
|
602
|
+
width: 1px;
|
|
603
|
+
height: 100%;
|
|
604
|
+
background-color: currentColor;
|
|
605
|
+
opacity: 0.2;
|
|
606
|
+
transform: translateX(-50%);
|
|
607
|
+
}
|
|
305
608
|
</style>
|
package/setup/shiki.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import setups from '#slidev/setups/shiki'
|
|
2
2
|
import { createSingletonPromise } from '@antfu/utils'
|
|
3
3
|
import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
|
|
4
|
-
import {
|
|
4
|
+
import { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'
|
|
5
5
|
import { resolveShikiOptions, shikiContext } from './shiki-options'
|
|
6
6
|
|
|
7
7
|
export default createSingletonPromise(async () => {
|
|
8
8
|
const { options, languageNames, languageInput, themeOption, themeNames, themeInput } = resolveShikiOptions(await Promise.all(setups.map(setup => setup(shikiContext))))
|
|
9
9
|
|
|
10
|
-
const createHighlighter =
|
|
10
|
+
const createHighlighter = createBundledHighlighter<string, string>({
|
|
11
11
|
engine: createJavaScriptRegexEngine,
|
|
12
12
|
langs: languageInput,
|
|
13
13
|
themes: themeInput,
|