@slidev/client 0.50.0-beta.10 → 0.50.0-beta.12
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/composables/useClicks.ts +6 -5
- package/composables/useDragElements.ts +30 -24
- package/composables/useNav.ts +17 -15
- package/composables/usePrintStyles.ts +28 -0
- package/constants.ts +1 -0
- package/internals/ExportPdfTip.vue +90 -0
- package/internals/FormCheckbox.vue +16 -0
- package/internals/FormItem.vue +41 -0
- package/internals/IconButton.vue +7 -2
- package/internals/NavControls.vue +9 -3
- package/internals/PrintContainer.vue +2 -21
- package/internals/PrintSlide.vue +4 -3
- package/internals/PrintSlideClick.vue +11 -3
- package/internals/Settings.vue +5 -2
- package/internals/SlidesShow.vue +7 -3
- package/logic/screenshot.ts +61 -0
- package/logic/shortcuts.ts +36 -35
- package/logic/slides.ts +2 -1
- package/modules/v-mark.ts +6 -0
- package/package.json +15 -13
- package/pages/export.vue +369 -0
- package/pages/play.vue +1 -4
- package/pages/presenter.vue +9 -0
- package/pages/print.vue +0 -2
- package/setup/monaco.ts +14 -14
- package/setup/root.ts +6 -2
- package/setup/routes.ts +23 -12
- package/state/index.ts +2 -1
- package/styles/index.css +4 -3
- package/internals/PrintStyle.vue +0 -16
package/composables/useClicks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClicksContext, NormalizedRangeClickValue, NormalizedSingleClickValue, RawAtValue, RawSingleAtValue, SlideRoute } from '@slidev/types'
|
|
2
|
-
import type { Ref } from 'vue'
|
|
2
|
+
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
3
3
|
import { clamp, sum } from '@antfu/utils'
|
|
4
|
-
import { computed, onMounted, onUnmounted, ref, shallowReactive } from 'vue'
|
|
4
|
+
import { computed, isReadonly, onMounted, onUnmounted, ref, shallowReactive, toValue } from 'vue'
|
|
5
5
|
|
|
6
6
|
export function normalizeSingleAtValue(at: RawSingleAtValue): NormalizedSingleClickValue {
|
|
7
7
|
if (at === false || at === 'false')
|
|
@@ -59,7 +59,8 @@ export function createClicksContextBase(
|
|
|
59
59
|
// Convert maxMap to reactive
|
|
60
60
|
maxMap = shallowReactive(maxMap)
|
|
61
61
|
// Make sure the query is not greater than the total
|
|
62
|
-
|
|
62
|
+
if (!isReadonly(current))
|
|
63
|
+
context.current = current.value
|
|
63
64
|
})
|
|
64
65
|
onUnmounted(() => {
|
|
65
66
|
isMounted.value = false
|
|
@@ -160,11 +161,11 @@ export function createClicksContextBase(
|
|
|
160
161
|
|
|
161
162
|
export function createFixedClicks(
|
|
162
163
|
route?: SlideRoute | undefined,
|
|
163
|
-
currentInit = 0,
|
|
164
|
+
currentInit: MaybeRefOrGetter<number> = 0,
|
|
164
165
|
): ClicksContext {
|
|
165
166
|
const clicksStart = route?.meta.slide?.frontmatter.clicksStart ?? 0
|
|
166
167
|
return createClicksContextBase(
|
|
167
|
-
|
|
168
|
+
computed(() => Math.max(toValue(currentInit), clicksStart)),
|
|
168
169
|
clicksStart,
|
|
169
170
|
route?.meta?.clicks,
|
|
170
171
|
)
|
|
@@ -7,6 +7,7 @@ import { injectionCurrentPage, injectionFrontmatter, injectionRenderContext, inj
|
|
|
7
7
|
import { makeId } from '../logic/utils'
|
|
8
8
|
import { activeDragElement } from '../state'
|
|
9
9
|
import { directiveInject } from '../utils'
|
|
10
|
+
import { useNav } from './useNav'
|
|
10
11
|
import { useSlideBounds } from './useSlideBounds'
|
|
11
12
|
import { useDynamicSlideInfo } from './useSlideInfo'
|
|
12
13
|
|
|
@@ -70,24 +71,24 @@ export function useDragElementsUpdater(no: number) {
|
|
|
70
71
|
section = type === 'prop'
|
|
71
72
|
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
|
72
73
|
? section.replace(/<(v-?drag-?\w*)(.*?)(\/)?>/gi, (full, tag, attrs, selfClose = '', index) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
if (index === idx) {
|
|
75
|
+
replaced = true
|
|
76
|
+
const posMatch = attrs.match(/pos=".*?"/)
|
|
77
|
+
if (!posMatch)
|
|
78
|
+
return `<${tag}${ensureSuffix(' ', attrs)}pos="${posStr}"${selfClose}>`
|
|
79
|
+
const start = posMatch.index
|
|
80
|
+
const end = start + posMatch[0].length
|
|
81
|
+
return `<${tag}${attrs.slice(0, start)}pos="${posStr}"${attrs.slice(end)}${selfClose}>`
|
|
82
|
+
}
|
|
83
|
+
return full
|
|
84
|
+
})
|
|
84
85
|
: section.replace(/(?<![</\w])v-drag(?:=".*?")?/gi, (full, index) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
if (index === idx) {
|
|
87
|
+
replaced = true
|
|
88
|
+
return `v-drag="${posStr}"`
|
|
89
|
+
}
|
|
90
|
+
return full
|
|
91
|
+
})
|
|
91
92
|
|
|
92
93
|
if (!replaced)
|
|
93
94
|
throw new Error(`[Slidev] VDrag Element ${id} is not found in the markdown source`)
|
|
@@ -127,7 +128,8 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
|
|
|
127
128
|
const scale = inject(injectionSlideScale) ?? ref(1)
|
|
128
129
|
const zoom = inject(injectionSlideZoom) ?? ref(1)
|
|
129
130
|
const { left: slideLeft, top: slideTop, stop: stopWatchBounds } = useSlideBounds(inject(injectionSlideElement) ?? ref())
|
|
130
|
-
const
|
|
131
|
+
const { isPrintMode } = useNav()
|
|
132
|
+
const enabled = ['slide', 'presenter'].includes(renderContext.value) && !isPrintMode.value
|
|
131
133
|
|
|
132
134
|
let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'
|
|
133
135
|
let dragId: string = makeId()
|
|
@@ -180,16 +182,16 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
|
|
|
180
182
|
const configuredHeight = ref(pos[3] ?? 0)
|
|
181
183
|
const height = autoHeight
|
|
182
184
|
? computed({
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
|
|
186
|
+
set: v => !autoHeight && (configuredHeight.value = v),
|
|
187
|
+
})
|
|
186
188
|
: configuredHeight
|
|
187
189
|
const configuredY0 = autoHeight ? ref(pos[1]) : ref(pos[1] + pos[3] / 2)
|
|
188
190
|
const y0 = autoHeight
|
|
189
191
|
? computed({
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
get: () => configuredY0.value + height.value / 2,
|
|
193
|
+
set: v => configuredY0.value = v - height.value / 2,
|
|
194
|
+
})
|
|
193
195
|
: configuredY0
|
|
194
196
|
|
|
195
197
|
const containerStyle = computed(() => {
|
|
@@ -266,10 +268,14 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
|
|
|
266
268
|
state.stopDragging()
|
|
267
269
|
},
|
|
268
270
|
startDragging(): void {
|
|
271
|
+
if (!enabled)
|
|
272
|
+
return
|
|
269
273
|
updateBounds()
|
|
270
274
|
activeDragElement.value = state
|
|
271
275
|
},
|
|
272
276
|
stopDragging(): void {
|
|
277
|
+
if (!enabled)
|
|
278
|
+
return
|
|
273
279
|
if (activeDragElement.value === state)
|
|
274
280
|
activeDragElement.value = null
|
|
275
281
|
},
|
package/composables/useNav.ts
CHANGED
|
@@ -3,10 +3,10 @@ import type { ComputedRef, Ref, TransitionGroupProps, WritableComputedRef } from
|
|
|
3
3
|
import type { RouteLocationNormalized, Router } from 'vue-router'
|
|
4
4
|
import { slides } from '#slidev/slides'
|
|
5
5
|
import { clamp } from '@antfu/utils'
|
|
6
|
+
import { parseRangeString } from '@slidev/parser/utils'
|
|
6
7
|
import { createSharedComposable } from '@vueuse/core'
|
|
7
|
-
import { logicOr } from '@vueuse/math'
|
|
8
8
|
import { computed, ref, watch } from 'vue'
|
|
9
|
-
import { useRouter } from 'vue-router'
|
|
9
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
10
10
|
import { CLICKS_MAX } from '../constants'
|
|
11
11
|
import { configs } from '../env'
|
|
12
12
|
import { skipTransition } from '../logic/hmr'
|
|
@@ -71,7 +71,7 @@ export interface SlidevContextNavState {
|
|
|
71
71
|
router: Router
|
|
72
72
|
currentRoute: ComputedRef<RouteLocationNormalized>
|
|
73
73
|
isPrintMode: ComputedRef<boolean>
|
|
74
|
-
isPrintWithClicks:
|
|
74
|
+
isPrintWithClicks: Ref<boolean>
|
|
75
75
|
isEmbedded: ComputedRef<boolean>
|
|
76
76
|
isPlaying: ComputedRef<boolean>
|
|
77
77
|
isPresenter: ComputedRef<boolean>
|
|
@@ -83,6 +83,7 @@ export interface SlidevContextNavState {
|
|
|
83
83
|
clicksContext: ComputedRef<ClicksContext>
|
|
84
84
|
queryClicksRaw: Ref<string>
|
|
85
85
|
queryClicks: WritableComputedRef<number>
|
|
86
|
+
printRange: Ref<number[]>
|
|
86
87
|
getPrimaryClicks: (route: SlideRoute) => ClicksContext
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -113,7 +114,7 @@ export function useNavBase(
|
|
|
113
114
|
const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)
|
|
114
115
|
const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)
|
|
115
116
|
|
|
116
|
-
const currentTransition = computed(() => getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
|
|
117
|
+
const currentTransition = computed(() => isPrint.value ? undefined : getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
|
|
117
118
|
|
|
118
119
|
watch(currentSlideRoute, (next, prev) => {
|
|
119
120
|
navDirection.value = next.no - prev.no
|
|
@@ -191,7 +192,7 @@ export function useNavBase(
|
|
|
191
192
|
clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)
|
|
192
193
|
if (force || pageChanged || clicksChanged) {
|
|
193
194
|
await router?.push({
|
|
194
|
-
path: getSlidePath(no, isPresenter.value),
|
|
195
|
+
path: getSlidePath(no, isPresenter.value, router.currentRoute.value.name === 'export'),
|
|
195
196
|
query: {
|
|
196
197
|
...router.currentRoute.value.query,
|
|
197
198
|
clicks: clicks === 0 ? undefined : clicks.toString(),
|
|
@@ -272,24 +273,24 @@ export function useFixedNav(
|
|
|
272
273
|
|
|
273
274
|
const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
274
275
|
const router = useRouter()
|
|
276
|
+
const currentRoute = useRoute()
|
|
275
277
|
|
|
276
|
-
const currentRoute = computed(() => router.currentRoute.value)
|
|
277
278
|
const query = computed(() => {
|
|
278
279
|
// eslint-disable-next-line ts/no-unused-expressions
|
|
279
280
|
router.currentRoute.value.query
|
|
280
281
|
return new URLSearchParams(location.search)
|
|
281
282
|
})
|
|
282
|
-
const isPrintMode = computed(() => query.value.has('print'))
|
|
283
|
-
const isPrintWithClicks =
|
|
283
|
+
const isPrintMode = computed(() => query.value.has('print') || currentRoute.name === 'export')
|
|
284
|
+
const isPrintWithClicks = ref(query.value.get('print') === 'clicks')
|
|
284
285
|
const isEmbedded = computed(() => query.value.has('embedded'))
|
|
285
|
-
const isPlaying = computed(() => currentRoute.
|
|
286
|
-
const isPresenter = computed(() => currentRoute.
|
|
287
|
-
const isNotesViewer = computed(() => currentRoute.
|
|
286
|
+
const isPlaying = computed(() => currentRoute.name === 'play')
|
|
287
|
+
const isPresenter = computed(() => currentRoute.name === 'presenter')
|
|
288
|
+
const isNotesViewer = computed(() => currentRoute.name === 'notes')
|
|
288
289
|
const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || query.value.get('password') === configs.remote))
|
|
289
|
-
const hasPrimarySlide =
|
|
290
|
-
|
|
291
|
-
const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1)
|
|
290
|
+
const hasPrimarySlide = computed(() => !!currentRoute.params.no)
|
|
291
|
+
const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.params.no as string)?.no ?? 1 : 1)
|
|
292
292
|
const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])
|
|
293
|
+
const printRange = ref(parseRangeString(slides.value.length, currentRoute.query.range as string | undefined))
|
|
293
294
|
|
|
294
295
|
const queryClicksRaw = useRouteQuery<string>('clicks', '0')
|
|
295
296
|
|
|
@@ -342,7 +343,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
|
342
343
|
|
|
343
344
|
return {
|
|
344
345
|
router,
|
|
345
|
-
currentRoute,
|
|
346
|
+
currentRoute: computed(() => currentRoute),
|
|
346
347
|
isPrintMode,
|
|
347
348
|
isPrintWithClicks,
|
|
348
349
|
isEmbedded,
|
|
@@ -356,6 +357,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
|
356
357
|
clicksContext,
|
|
357
358
|
queryClicksRaw,
|
|
358
359
|
queryClicks,
|
|
360
|
+
printRange,
|
|
359
361
|
getPrimaryClicks,
|
|
360
362
|
}
|
|
361
363
|
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useStyleTag } from '@vueuse/core'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { slideHeight, slideWidth } from '../env'
|
|
4
|
+
import { useNav } from './useNav'
|
|
5
|
+
|
|
6
|
+
export function usePrintStyles() {
|
|
7
|
+
const { isPrintMode } = useNav()
|
|
8
|
+
|
|
9
|
+
useStyleTag(computed(() => isPrintMode.value
|
|
10
|
+
? `
|
|
11
|
+
@page {
|
|
12
|
+
size: ${slideWidth.value}px ${slideHeight.value}px;
|
|
13
|
+
margin: 0px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
* {
|
|
17
|
+
transition: none !important;
|
|
18
|
+
transition-duration: 0s !important;
|
|
19
|
+
}`
|
|
20
|
+
: ''))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Monaco uses `<style media="screen" class="monaco-colors">` to apply colors, which will be ignored in print mode.
|
|
24
|
+
export function patchMonacoColors() {
|
|
25
|
+
document.querySelectorAll<HTMLStyleElement>('style.monaco-colors').forEach((el) => {
|
|
26
|
+
el.media = ''
|
|
27
|
+
})
|
|
28
|
+
}
|
package/constants.ts
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useVModel } from '@vueuse/core'
|
|
3
|
+
import { skipExportPdfTip } from '../state'
|
|
4
|
+
import Modal from './Modal.vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
modelValue: {
|
|
8
|
+
default: false,
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits(['update:modelValue', 'print'])
|
|
13
|
+
const value = useVModel(props, 'modelValue', emit)
|
|
14
|
+
|
|
15
|
+
function print() {
|
|
16
|
+
value.value = false
|
|
17
|
+
emit('print')
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<Modal v-model="value" class="px-6 py-4 flex flex-col gap-2">
|
|
23
|
+
<div class="flex gap-2 text-xl">
|
|
24
|
+
<div class="i-carbon:information my-auto" /> Tips
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
Slidev will open your browser's built-in print dialog to export the slides as PDF. <br>
|
|
28
|
+
In the print dialog, please:
|
|
29
|
+
<ul class="list-disc my-4 pl-4">
|
|
30
|
+
<li>
|
|
31
|
+
Choose "Save as PDF" as the Destination.
|
|
32
|
+
<span class="op-70 text-xs"> (Not "Microsoft Print to PDF") </span>
|
|
33
|
+
</li>
|
|
34
|
+
<li> Choose "Default" as the Margin. </li>
|
|
35
|
+
<li> Toggle on "Print backgrounds". </li>
|
|
36
|
+
</ul>
|
|
37
|
+
<div class="mb-2 op-70 text-sm">
|
|
38
|
+
If you're encountering problems, please try
|
|
39
|
+
<a href="https://sli.dev/builtin/cli#export"> the CLI </a>
|
|
40
|
+
or
|
|
41
|
+
<a href="https://github.com/slidevjs/slidev/issues/new"> open an issue</a>.
|
|
42
|
+
</div>
|
|
43
|
+
<div class="form-check op-70">
|
|
44
|
+
<input
|
|
45
|
+
v-model="skipExportPdfTip"
|
|
46
|
+
name="record-camera"
|
|
47
|
+
type="checkbox"
|
|
48
|
+
>
|
|
49
|
+
<label for="record-camera" @click="skipExportPdfTip = !skipExportPdfTip">Don't show this dialog next time.</label>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="flex my-1">
|
|
53
|
+
<button class="cancel" @click="value = false">
|
|
54
|
+
Cancel
|
|
55
|
+
</button>
|
|
56
|
+
<div class="flex-auto" />
|
|
57
|
+
<button @click="print">
|
|
58
|
+
Start
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</Modal>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
button {
|
|
66
|
+
@apply bg-blue-400 text-white px-4 py-1 rounded border-b-2 border-blue-600;
|
|
67
|
+
@apply hover:(bg-blue-500 border-blue-700);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
button.cancel {
|
|
71
|
+
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
|
|
72
|
+
@apply hover:(bg-opacity-75 border-opacity-75);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
a {
|
|
76
|
+
@apply border-current border-b border-dashed hover:text-primary hover:border-solid;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.form-check {
|
|
80
|
+
@apply leading-5;
|
|
81
|
+
|
|
82
|
+
* {
|
|
83
|
+
@apply my-auto align-middle;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
label {
|
|
87
|
+
@apply ml-1 text-sm select-none;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
disabled?: boolean
|
|
4
|
+
}>()
|
|
5
|
+
|
|
6
|
+
const value = defineModel<boolean>('modelValue', {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
})
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div border="~ main rounded" flex="~ gap-2 items-center" relative h-5 w-5 p0.5 hover:bg-active p1>
|
|
13
|
+
<div i-ri-check-line :class="value ? '' : 'op0'" />
|
|
14
|
+
<input v-model="value" type="checkbox" absolute inset-0 z-10 opacity-0.1 :disabled="disabled">
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tooltip } from 'floating-vue'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
title: string
|
|
6
|
+
nested?: boolean | number
|
|
7
|
+
div?: boolean
|
|
8
|
+
description?: string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
(event: 'reset'): void
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
function reset() {
|
|
16
|
+
emit('reset')
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<component :is="div ? 'div' : 'label'" flex="~ row gap-2 items-center" select-none>
|
|
22
|
+
<div w-30 h-10 flex="~ gap-1 items-center">
|
|
23
|
+
<div
|
|
24
|
+
v-if="nested" i-ri-corner-down-right-line op40
|
|
25
|
+
:style="typeof nested === 'number' ? { marginLeft: `${nested * 0.5 + 0.5}rem` } : { marginLeft: '0.25rem' }"
|
|
26
|
+
/>
|
|
27
|
+
<div v-if="!description" op75 @dblclick="reset">
|
|
28
|
+
{{ title }}
|
|
29
|
+
</div>
|
|
30
|
+
<Tooltip v-else distance="10">
|
|
31
|
+
<div op75 text-right @dblclick="reset">
|
|
32
|
+
{{ title }}
|
|
33
|
+
</div>
|
|
34
|
+
<template #popper>
|
|
35
|
+
<div text-sm min-w-90 v-html="description" />
|
|
36
|
+
</template>
|
|
37
|
+
</Tooltip>
|
|
38
|
+
</div>
|
|
39
|
+
<slot />
|
|
40
|
+
</component>
|
|
41
|
+
</template>
|
package/internals/IconButton.vue
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
3
5
|
title: string
|
|
4
6
|
icon?: string
|
|
5
7
|
as?: string
|
|
8
|
+
to?: string
|
|
6
9
|
}>()
|
|
10
|
+
|
|
11
|
+
const type = computed(() => props.as || (props.to ? 'router-link' : 'button'))
|
|
7
12
|
</script>
|
|
8
13
|
|
|
9
14
|
<template>
|
|
10
|
-
<component :is="
|
|
15
|
+
<component :is="type" class="slidev-icon-btn" :title="title" :to="to">
|
|
11
16
|
<span class="sr-only">{{ title }}</span>
|
|
12
17
|
<slot>
|
|
13
18
|
<div :class="icon" />
|
|
@@ -58,7 +58,7 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
58
58
|
<template>
|
|
59
59
|
<nav ref="root" class="flex flex-col">
|
|
60
60
|
<div
|
|
61
|
-
class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:
|
|
61
|
+
class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:p-2"
|
|
62
62
|
:class="barStyle"
|
|
63
63
|
@mouseleave="onMouseLeave"
|
|
64
64
|
>
|
|
@@ -131,7 +131,7 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
131
131
|
<div class="i-carbon:text-annotation-toggle" />
|
|
132
132
|
</IconButton>
|
|
133
133
|
|
|
134
|
-
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial" @click="togglePresenterLayout">
|
|
134
|
+
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
|
|
135
135
|
<div class="i-carbon:template" />
|
|
136
136
|
{{ presenterLayout }}
|
|
137
137
|
</IconButton>
|
|
@@ -142,6 +142,12 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
142
142
|
</IconButton>
|
|
143
143
|
</template>
|
|
144
144
|
|
|
145
|
+
<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__">
|
|
146
|
+
<IconButton title="Browser Exporter" to="/export">
|
|
147
|
+
<div class="i-carbon:document-pdf" />
|
|
148
|
+
</IconButton>
|
|
149
|
+
</template>
|
|
150
|
+
|
|
145
151
|
<IconButton
|
|
146
152
|
v-if="!isPresenter && configs.info && !isEmbedded"
|
|
147
153
|
title="Show info"
|
|
@@ -153,7 +159,7 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
153
159
|
<template v-if="!isPresenter && !isEmbedded">
|
|
154
160
|
<MenuButton>
|
|
155
161
|
<template #button>
|
|
156
|
-
<IconButton title="
|
|
162
|
+
<IconButton title="More Options">
|
|
157
163
|
<div class="i-carbon:settings-adjust" />
|
|
158
164
|
</IconButton>
|
|
159
165
|
</template>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { parseRangeString } from '@slidev/parser/utils'
|
|
3
2
|
import { provideLocal } from '@vueuse/core'
|
|
4
3
|
import { computed } from 'vue'
|
|
5
4
|
import { useNav } from '../composables/useNav'
|
|
@@ -11,7 +10,7 @@ const props = defineProps<{
|
|
|
11
10
|
width: number
|
|
12
11
|
}>()
|
|
13
12
|
|
|
14
|
-
const { slides,
|
|
13
|
+
const { slides, printRange } = useNav()
|
|
15
14
|
|
|
16
15
|
const width = computed(() => props.width)
|
|
17
16
|
const height = computed(() => props.width / slideAspect.value)
|
|
@@ -24,13 +23,6 @@ const scale = computed(() => {
|
|
|
24
23
|
return (height.value * slideAspect.value) / slideWidth.value
|
|
25
24
|
})
|
|
26
25
|
|
|
27
|
-
// In print mode, the routes will never change. So we don't need reactivity here.
|
|
28
|
-
let routes = slides.value
|
|
29
|
-
if (currentRoute.value.query.range) {
|
|
30
|
-
const r = parseRangeString(routes.length, currentRoute.value.query.range as string)
|
|
31
|
-
routes = r.map(i => routes[i - 1])
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
const className = computed(() => ({
|
|
35
27
|
'select-none': !configs.selectable,
|
|
36
28
|
}))
|
|
@@ -41,18 +33,7 @@ provideLocal(injectionSlideScale, scale)
|
|
|
41
33
|
<template>
|
|
42
34
|
<div id="print-container" :class="className">
|
|
43
35
|
<div id="print-content">
|
|
44
|
-
<PrintSlide v-for="
|
|
36
|
+
<PrintSlide v-for="no of printRange" :key="no" :route="slides[no - 1]" />
|
|
45
37
|
</div>
|
|
46
|
-
<slot name="controls" />
|
|
47
38
|
</div>
|
|
48
39
|
</template>
|
|
49
|
-
|
|
50
|
-
<style lang="postcss">
|
|
51
|
-
#print-content {
|
|
52
|
-
@apply bg-main;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.print-slide-container {
|
|
56
|
-
@apply relative overflow-hidden break-after-page translate-0;
|
|
57
|
-
}
|
|
58
|
-
</style>
|
package/internals/PrintSlide.vue
CHANGED
|
@@ -5,14 +5,14 @@ import { useFixedNav, useNav } from '../composables/useNav'
|
|
|
5
5
|
import { CLICKS_MAX } from '../constants'
|
|
6
6
|
import PrintSlideClick from './PrintSlideClick.vue'
|
|
7
7
|
|
|
8
|
-
const { route } = defineProps<{ route: SlideRoute }>()
|
|
8
|
+
const { route } = defineProps<{ hidden?: boolean, route: SlideRoute }>()
|
|
9
9
|
const { isPrintWithClicks } = useNav()
|
|
10
|
-
const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MAX)
|
|
10
|
+
const clicks0 = createFixedClicks(route, () => isPrintWithClicks.value ? 0 : CLICKS_MAX)
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<template>
|
|
14
14
|
<PrintSlideClick
|
|
15
|
-
|
|
15
|
+
v-show="!hidden"
|
|
16
16
|
:nav="useFixedNav(route, clicks0)"
|
|
17
17
|
/>
|
|
18
18
|
<template v-if="isPrintWithClicks">
|
|
@@ -22,6 +22,7 @@ const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MA
|
|
|
22
22
|
-->
|
|
23
23
|
<PrintSlideClick
|
|
24
24
|
v-for="i in Math.max(0, clicks0.total - clicks0.clicksStart)"
|
|
25
|
+
v-show="!hidden"
|
|
25
26
|
:key="i"
|
|
26
27
|
:nav="useFixedNav(route, createFixedClicks(route, i + clicks0.clicksStart))"
|
|
27
28
|
/>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import type { SlidevContextNav } from '../composables/useNav'
|
|
3
3
|
import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
|
|
4
4
|
import { provideLocal } from '@vueuse/core'
|
|
5
|
-
import { computed, reactive, shallowRef } from 'vue'
|
|
6
|
-
import { injectionSlidevContext } from '../constants'
|
|
5
|
+
import { computed, reactive, shallowRef, useTemplateRef } from 'vue'
|
|
6
|
+
import { injectionSlideElement, injectionSlidevContext } from '../constants'
|
|
7
7
|
import { configs, slideHeight, slideWidth } from '../env'
|
|
8
8
|
import { getSlideClass } from '../utils'
|
|
9
9
|
import SlideWrapper from './SlideWrapper.vue'
|
|
@@ -32,10 +32,12 @@ provideLocal(injectionSlidevContext, reactive({
|
|
|
32
32
|
configs,
|
|
33
33
|
themeConfigs: computed(() => configs.themeConfig),
|
|
34
34
|
}))
|
|
35
|
+
|
|
36
|
+
provideLocal(injectionSlideElement, useTemplateRef('slide-element'))
|
|
35
37
|
</script>
|
|
36
38
|
|
|
37
39
|
<template>
|
|
38
|
-
<div :id="id" class="print-slide-container" :style="style">
|
|
40
|
+
<div :id="id" ref="slide-element" class="print-slide-container" :style="style">
|
|
39
41
|
<GlobalBottom />
|
|
40
42
|
|
|
41
43
|
<SlideWrapper
|
|
@@ -56,3 +58,9 @@ provideLocal(injectionSlidevContext, reactive({
|
|
|
56
58
|
<GlobalTop />
|
|
57
59
|
</div>
|
|
58
60
|
</template>
|
|
61
|
+
|
|
62
|
+
<style scoped lang="postcss">
|
|
63
|
+
.print-slide-container {
|
|
64
|
+
@apply relative overflow-hidden break-after-page translate-0 bg-main;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
package/internals/Settings.vue
CHANGED
|
@@ -30,8 +30,11 @@ const wakeLockItems: SelectionItem<boolean>[] = [
|
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<template>
|
|
33
|
-
<div class="text-sm select-none">
|
|
33
|
+
<div class="text-sm select-none mb-2">
|
|
34
34
|
<SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
|
|
35
|
-
<SelectList
|
|
35
|
+
<SelectList
|
|
36
|
+
v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
|
|
37
|
+
v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
|
|
38
|
+
/>
|
|
36
39
|
</div>
|
|
37
40
|
</template>
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
isPrintMode,
|
|
27
27
|
isPrintWithClicks,
|
|
28
28
|
clicksDirection,
|
|
29
|
+
printRange,
|
|
29
30
|
} = useNav()
|
|
30
31
|
|
|
31
32
|
function preloadRoute(route: SlideRoute) {
|
|
@@ -55,7 +56,10 @@ const DrawingLayer = shallowRef<any>()
|
|
|
55
56
|
if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
56
57
|
import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
|
|
57
58
|
|
|
58
|
-
const loadedRoutes = computed(() =>
|
|
59
|
+
const loadedRoutes = computed(() => isPrintMode.value
|
|
60
|
+
? printRange.value.map(no => slides.value[no - 1])
|
|
61
|
+
: slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value),
|
|
62
|
+
)
|
|
59
63
|
|
|
60
64
|
function onAfterLeave() {
|
|
61
65
|
// After transition, we disable it so HMR won't trigger it again
|
|
@@ -72,8 +76,8 @@ function onAfterLeave() {
|
|
|
72
76
|
|
|
73
77
|
<!-- Slides -->
|
|
74
78
|
<component
|
|
75
|
-
:is="hasViewTransition ? 'div' : TransitionGroup"
|
|
76
|
-
v-bind="skipTransition ? {} : currentTransition"
|
|
79
|
+
:is="hasViewTransition && !isPrintMode ? 'div' : TransitionGroup"
|
|
80
|
+
v-bind="skipTransition || isPrintMode ? {} : currentTransition"
|
|
77
81
|
id="slideshow"
|
|
78
82
|
tag="div"
|
|
79
83
|
:class="{
|