@slidev/client 0.50.0-beta.9 → 0.51.0-beta.1
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/composables/useTimer.ts +1 -1
- package/constants.ts +1 -0
- package/internals/Badge.vue +48 -0
- package/internals/ClicksSlider.vue +1 -1
- package/internals/ContextMenu.vue +1 -1
- package/internals/{DevicesList.vue → DevicesSelectors.vue} +12 -4
- package/internals/DragControl.vue +1 -1
- package/internals/DrawingControls.vue +1 -1
- 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/MenuButton.vue +2 -2
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +21 -9
- package/internals/PrintContainer.vue +2 -21
- package/internals/PrintSlide.vue +4 -3
- package/internals/PrintSlideClick.vue +11 -3
- package/internals/QuickOverview.vue +2 -2
- package/internals/RecordingControls.vue +2 -2
- package/internals/RecordingDialog.vue +4 -14
- package/internals/ScreenCaptureMirror.vue +45 -0
- package/internals/SegmentControl.vue +29 -0
- package/internals/SelectList.vue +1 -5
- package/internals/Settings.vue +16 -3
- package/internals/SideEditor.vue +1 -1
- package/internals/SlidesShow.vue +7 -3
- package/internals/SyncControls.vue +73 -0
- package/internals/WebCamera.vue +2 -2
- package/layouts/error.vue +5 -1
- package/logic/color.ts +62 -0
- package/logic/dark.ts +11 -0
- 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 +20 -18
- package/pages/export.vue +365 -0
- package/pages/notes.vue +3 -3
- package/pages/overview.vue +1 -1
- package/pages/play.vue +1 -4
- package/pages/presenter.vue +46 -18
- package/pages/print.vue +0 -2
- package/setup/monaco.ts +14 -14
- package/setup/root.ts +37 -25
- package/setup/routes.ts +23 -12
- package/state/drawings.ts +5 -1
- package/state/index.ts +1 -55
- package/state/shared.ts +0 -7
- package/state/storage.ts +70 -0
- package/styles/index.css +15 -4
- package/uno.config.ts +15 -0
- 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
|
-
ref(Math.max(currentInit, clicksStart)),
|
|
168
|
+
ref(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/composables/useTimer.ts
CHANGED
package/constants.ts
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
getHashColorFromString,
|
|
5
|
+
getHsla,
|
|
6
|
+
} from '../logic/color'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
text?: string
|
|
11
|
+
color?: boolean | number
|
|
12
|
+
as?: string
|
|
13
|
+
size?: string
|
|
14
|
+
}>(),
|
|
15
|
+
{
|
|
16
|
+
color: true,
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const style = computed(() => {
|
|
21
|
+
if (!props.text || props.color === false)
|
|
22
|
+
return {}
|
|
23
|
+
return {
|
|
24
|
+
color: typeof props.color === 'number'
|
|
25
|
+
? getHsla(props.color)
|
|
26
|
+
: getHashColorFromString(props.text),
|
|
27
|
+
background: typeof props.color === 'number'
|
|
28
|
+
? getHsla(props.color, 0.1)
|
|
29
|
+
: getHashColorFromString(props.text, 0.1),
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const sizeClasses = computed(() => {
|
|
34
|
+
switch (props.size || 'sm') {
|
|
35
|
+
case 'sm':
|
|
36
|
+
return 'px-1.5 text-11px leading-1.6em'
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<component :is="as || 'span'" ws-nowrap rounded :class="sizeClasses" :style>
|
|
44
|
+
<slot>
|
|
45
|
+
<span v-text="props.text" />
|
|
46
|
+
</slot>
|
|
47
|
+
</component>
|
|
48
|
+
</template>
|
|
@@ -87,7 +87,7 @@ function onMousedown() {
|
|
|
87
87
|
v-model="current"
|
|
88
88
|
class="range"
|
|
89
89
|
type="range" :min="start" :max="total" :step="1"
|
|
90
|
-
absolute inset-0 z-
|
|
90
|
+
absolute inset-0 z-label op0
|
|
91
91
|
:class="readonly ? 'pointer-events-none' : ''"
|
|
92
92
|
:style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
|
|
93
93
|
@mousedown="onMousedown"
|
|
@@ -61,7 +61,7 @@ const top = computed(() => {
|
|
|
61
61
|
v-if="currentContextMenu"
|
|
62
62
|
ref="container"
|
|
63
63
|
:style="`left:${left}px;top:${top}px`"
|
|
64
|
-
class="fixed z-
|
|
64
|
+
class="slidev-glass-effect fixed z-context-menu w-60 flex flex-wrap justify-items-start p-1 animate-fade-in animate-duration-100 rounded-md shadow overflow-hidden select-none"
|
|
65
65
|
@contextmenu.prevent=""
|
|
66
66
|
@click="closeContextMenu"
|
|
67
67
|
>
|
|
@@ -43,13 +43,21 @@ ensureDevicesListPermissions()
|
|
|
43
43
|
</script>
|
|
44
44
|
|
|
45
45
|
<template>
|
|
46
|
-
<div
|
|
47
|
-
<SelectList
|
|
48
|
-
|
|
46
|
+
<div text-sm flex="~ col gap-2">
|
|
47
|
+
<SelectList
|
|
48
|
+
v-model="currentCamera"
|
|
49
|
+
title="Camera"
|
|
50
|
+
:items="camerasItems"
|
|
51
|
+
/>
|
|
52
|
+
<SelectList
|
|
53
|
+
v-model="currentMic"
|
|
54
|
+
title="Microphone"
|
|
55
|
+
:items="microphonesItems"
|
|
56
|
+
/>
|
|
49
57
|
<SelectList
|
|
50
58
|
v-if="mimeTypeItems.length"
|
|
51
59
|
v-model="mimeType"
|
|
52
|
-
title="
|
|
60
|
+
title="Video Format"
|
|
53
61
|
:items="mimeTypeItems"
|
|
54
62
|
/>
|
|
55
63
|
</div>
|
|
@@ -375,7 +375,7 @@ watchEffect(() => {
|
|
|
375
375
|
@pointermove="onPointermove"
|
|
376
376
|
@pointerup="onPointerup"
|
|
377
377
|
>
|
|
378
|
-
<div class="absolute inset-0 z-
|
|
378
|
+
<div class="absolute inset-0 z-nav dark:b-gray-400" :class="isArrow ? '' : 'b b-dark'">
|
|
379
379
|
<template v-if="!autoHeight">
|
|
380
380
|
<div v-bind="getCornerProps(true, true)" />
|
|
381
381
|
<div v-bind="getCornerProps(false, false)" />
|
|
@@ -42,7 +42,7 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
42
42
|
<template>
|
|
43
43
|
<Draggable
|
|
44
44
|
v-if="drawingEnabled || drawingPinned"
|
|
45
|
-
class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-
|
|
45
|
+
class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-nav border border-main"
|
|
46
46
|
:class="!drawingEnabled && drawingPinned ? 'opacity-40 hover:opacity-90' : ''"
|
|
47
47
|
storage-key="slidev-drawing-pos"
|
|
48
48
|
:initial-x="10"
|
|
@@ -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 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" />
|
package/internals/MenuButton.vue
CHANGED
|
@@ -30,8 +30,8 @@ onClickOutside(el, () => {
|
|
|
30
30
|
<KeepAlive>
|
|
31
31
|
<div
|
|
32
32
|
v-if="value"
|
|
33
|
-
class="
|
|
34
|
-
|
|
33
|
+
class="bg-main text-main shadow-xl absolute bottom-10 left-0 z-menu"
|
|
34
|
+
border="~ main rounded-md"
|
|
35
35
|
>
|
|
36
36
|
<slot name="menu" />
|
|
37
37
|
</div>
|
package/internals/Modal.vue
CHANGED
|
@@ -20,7 +20,7 @@ function onClick() {
|
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
22
|
<KeepAlive>
|
|
23
|
-
<div v-if="value" class="fixed top-0 bottom-0 left-0 right-0 grid z-
|
|
23
|
+
<div v-if="value" class="fixed top-0 bottom-0 left-0 right-0 grid z-modal">
|
|
24
24
|
<div
|
|
25
25
|
bg="black opacity-80"
|
|
26
26
|
class="absolute top-0 bottom-0 left-0 right-0 -z-1"
|
|
@@ -10,6 +10,7 @@ import { downloadPDF } from '../utils'
|
|
|
10
10
|
import IconButton from './IconButton.vue'
|
|
11
11
|
import MenuButton from './MenuButton.vue'
|
|
12
12
|
import Settings from './Settings.vue'
|
|
13
|
+
import SyncControls from './SyncControls.vue'
|
|
13
14
|
|
|
14
15
|
import VerticalDivider from './VerticalDivider.vue'
|
|
15
16
|
|
|
@@ -48,7 +49,7 @@ function onMouseLeave() {
|
|
|
48
49
|
|
|
49
50
|
const barStyle = computed(() => props.persist
|
|
50
51
|
? 'text-$slidev-controls-foreground bg-transparent'
|
|
51
|
-
: 'rounded-md bg-main shadow
|
|
52
|
+
: 'rounded-md bg-main shadow-xl border border-main')
|
|
52
53
|
|
|
53
54
|
const RecordingControls = shallowRef<any>()
|
|
54
55
|
if (__SLIDEV_FEATURE_RECORD__)
|
|
@@ -58,7 +59,7 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
58
59
|
<template>
|
|
59
60
|
<nav ref="root" class="flex flex-col">
|
|
60
61
|
<div
|
|
61
|
-
class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:
|
|
62
|
+
class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:p-2"
|
|
62
63
|
:class="barStyle"
|
|
63
64
|
@mouseleave="onMouseLeave"
|
|
64
65
|
>
|
|
@@ -130,18 +131,20 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
130
131
|
>
|
|
131
132
|
<div class="i-carbon:text-annotation-toggle" />
|
|
132
133
|
</IconButton>
|
|
133
|
-
|
|
134
|
-
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial" @click="togglePresenterLayout">
|
|
135
|
-
<div class="i-carbon:template" />
|
|
136
|
-
{{ presenterLayout }}
|
|
137
|
-
</IconButton>
|
|
138
134
|
</template>
|
|
135
|
+
|
|
139
136
|
<template v-if="!__DEV__">
|
|
140
137
|
<IconButton v-if="configs.download" title="Download as PDF" @click="downloadPDF">
|
|
141
138
|
<div class="i-carbon:download" />
|
|
142
139
|
</IconButton>
|
|
143
140
|
</template>
|
|
144
141
|
|
|
142
|
+
<template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__ && !isEmbedded && !isPresenter">
|
|
143
|
+
<IconButton title="Browser Exporter" to="/export">
|
|
144
|
+
<div class="i-carbon:document-pdf" />
|
|
145
|
+
</IconButton>
|
|
146
|
+
</template>
|
|
147
|
+
|
|
145
148
|
<IconButton
|
|
146
149
|
v-if="!isPresenter && configs.info && !isEmbedded"
|
|
147
150
|
title="Show info"
|
|
@@ -150,10 +153,19 @@ if (__SLIDEV_FEATURE_RECORD__)
|
|
|
150
153
|
<div class="i-carbon:information" />
|
|
151
154
|
</IconButton>
|
|
152
155
|
|
|
153
|
-
<template v-if="!
|
|
156
|
+
<template v-if="!isEmbedded">
|
|
157
|
+
<VerticalDivider />
|
|
158
|
+
|
|
159
|
+
<IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
|
|
160
|
+
<div class="i-carbon:template" />
|
|
161
|
+
{{ presenterLayout }}
|
|
162
|
+
</IconButton>
|
|
163
|
+
|
|
164
|
+
<SyncControls v-if="__SLIDEV_FEATURE_PRESENTER__" />
|
|
165
|
+
|
|
154
166
|
<MenuButton>
|
|
155
167
|
<template #button>
|
|
156
|
-
<IconButton title="
|
|
168
|
+
<IconButton title="More Options">
|
|
157
169
|
<div class="i-carbon:settings-adjust" />
|
|
158
170
|
</IconButton>
|
|
159
171
|
</template>
|