@slidev/client 0.48.0-beta.2 → 0.48.0-beta.20
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/App.vue +7 -0
- package/builtin/Arrow.vue +2 -4
- package/builtin/CodeBlockWrapper.vue +14 -6
- package/builtin/KaTexBlockWrapper.vue +5 -4
- package/builtin/Mermaid.vue +4 -3
- package/builtin/Monaco.vue +109 -92
- package/builtin/RenderWhen.vue +3 -3
- package/builtin/ShikiMagicMove.vue +50 -0
- package/builtin/SlideCurrentNo.vue +2 -3
- package/builtin/SlidesTotal.vue +3 -4
- package/builtin/SlidevVideo.vue +8 -6
- package/builtin/Toc.vue +3 -3
- package/builtin/TocList.vue +3 -2
- package/builtin/Tweet.vue +3 -22
- package/builtin/VClick.ts +2 -1
- package/builtin/VClickGap.vue +3 -5
- package/builtin/VClicks.ts +1 -1
- package/composables/useClicks.ts +34 -16
- package/constants.ts +58 -8
- package/context.ts +73 -0
- package/env.ts +3 -12
- package/internals/ClicksSlider.vue +93 -0
- package/internals/Controls.vue +2 -2
- package/internals/DrawingControls.vue +39 -9
- package/internals/DrawingLayer.vue +3 -3
- package/internals/Goto.vue +5 -4
- package/internals/IconButton.vue +7 -3
- package/internals/InfoDialog.vue +1 -1
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +4 -5
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +128 -0
- package/internals/NoteStatic.vue +8 -6
- package/internals/PrintContainer.vue +4 -3
- package/internals/PrintSlide.vue +8 -2
- package/internals/PrintSlideClick.vue +5 -7
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +21 -10
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +5 -6
- package/internals/{Editor.vue → SideEditor.vue} +7 -3
- package/internals/SlideContainer.vue +12 -9
- package/internals/SlideWrapper.ts +28 -12
- package/internals/SlidesShow.vue +7 -8
- package/layouts/two-cols-header.vue +9 -3
- package/logic/drawings.ts +6 -3
- package/logic/nav.ts +11 -8
- package/logic/note.ts +7 -7
- package/main.ts +8 -4
- package/modules/context.ts +4 -3
- package/modules/mermaid.ts +6 -7
- package/modules/{directives.ts → v-click.ts} +15 -15
- package/modules/v-mark.ts +159 -0
- package/package.json +26 -16
- package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
- package/{internals/NotesView.vue → pages/notes.vue} +5 -3
- package/pages/overview.vue +229 -0
- package/{internals/Play.vue → pages/play.vue} +15 -12
- package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +12 -7
- package/{internals/Presenter.vue → pages/presenter.vue} +108 -100
- package/{internals/Print.vue → pages/print.vue} +3 -4
- package/routes.ts +27 -51
- package/setup/codemirror.ts +8 -3
- package/setup/monaco.ts +108 -44
- package/setup/root.ts +2 -2
- package/shim-vue.d.ts +35 -0
- package/shim.d.ts +1 -13
- package/state/index.ts +10 -10
- package/styles/code.css +7 -3
- package/styles/index.css +68 -7
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +17 -12
- package/styles/monaco.css +27 -0
- package/styles/vars.css +1 -0
- package/uno.config.ts +14 -2
- package/iframes/monaco/index.css +0 -28
- package/iframes/monaco/index.html +0 -7
- package/iframes/monaco/index.ts +0 -260
- package/internals/NoteEditor.vue +0 -88
|
@@ -1,36 +1,159 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import type { ClicksContext } from '@slidev/types'
|
|
4
|
+
import { CLICKS_MAX } from '../constants'
|
|
5
|
+
|
|
2
6
|
const props = defineProps<{
|
|
3
7
|
class?: string
|
|
4
8
|
noteHtml?: string
|
|
5
9
|
note?: string
|
|
6
10
|
placeholder?: string
|
|
11
|
+
clicksContext?: ClicksContext
|
|
12
|
+
autoScroll?: boolean
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
(type: 'markerDblclick', e: MouseEvent, clicks: number): void
|
|
17
|
+
(type: 'markerClick', e: MouseEvent, clicks: number): void
|
|
7
18
|
}>()
|
|
8
19
|
|
|
9
|
-
|
|
20
|
+
const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark'))
|
|
21
|
+
const noteDisplay = ref<HTMLElement | null>(null)
|
|
22
|
+
|
|
23
|
+
const CLASS_FADE = 'slidev-note-fade'
|
|
24
|
+
const CLASS_MARKER = 'slidev-note-click-mark'
|
|
25
|
+
|
|
26
|
+
function highlightNote() {
|
|
27
|
+
if (!noteDisplay.value || !withClicks.value)
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
|
|
31
|
+
|
|
32
|
+
const current = +(props.clicksContext?.current ?? CLICKS_MAX)
|
|
33
|
+
const disabled = current < 0 || current >= CLICKS_MAX
|
|
34
|
+
|
|
35
|
+
const nodeToIgnores = new Set<Element>()
|
|
36
|
+
function ignoreParent(node: Element) {
|
|
37
|
+
if (!node || node === noteDisplay.value)
|
|
38
|
+
return
|
|
39
|
+
nodeToIgnores.add(node)
|
|
40
|
+
if (node.parentElement)
|
|
41
|
+
ignoreParent(node.parentElement)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const markersMap = new Map<number, HTMLElement>()
|
|
45
|
+
|
|
46
|
+
// Convert all sibling text nodes to spans, so we attach classes to them
|
|
47
|
+
for (const marker of markers) {
|
|
48
|
+
const parent = marker.parentElement!
|
|
49
|
+
const clicks = Number(marker.dataset!.clicks)
|
|
50
|
+
markersMap.set(clicks, marker)
|
|
51
|
+
// Ignore the parents of the marker, so the class only applies to the children
|
|
52
|
+
ignoreParent(parent)
|
|
53
|
+
Array.from(parent!.childNodes)
|
|
54
|
+
.forEach((node) => {
|
|
55
|
+
if (node.nodeType === 3) { // text node
|
|
56
|
+
const span = document.createElement('span')
|
|
57
|
+
span.textContent = node.textContent
|
|
58
|
+
parent.insertBefore(span, node)
|
|
59
|
+
node.remove()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
const children = Array.from(noteDisplay.value.querySelectorAll('*'))
|
|
64
|
+
|
|
65
|
+
let count = 0
|
|
66
|
+
|
|
67
|
+
// Segmenting notes by clicks
|
|
68
|
+
const segments = new Map<number, Element[]>()
|
|
69
|
+
for (const child of children) {
|
|
70
|
+
if (!segments.has(count))
|
|
71
|
+
segments.set(count, [])
|
|
72
|
+
segments.get(count)!.push(child)
|
|
73
|
+
// Update count when reach marker
|
|
74
|
+
if (child.classList.contains(CLASS_MARKER))
|
|
75
|
+
count = Number((child as HTMLElement).dataset.clicks) || (count + 1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Apply
|
|
79
|
+
for (const [count, els] of segments) {
|
|
80
|
+
if (disabled) {
|
|
81
|
+
els.forEach(el => el.classList.remove(CLASS_FADE))
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
els.forEach(el => el.classList.toggle(
|
|
85
|
+
CLASS_FADE,
|
|
86
|
+
nodeToIgnores.has(el)
|
|
87
|
+
? false
|
|
88
|
+
: count !== current,
|
|
89
|
+
))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const [clicks, marker] of markersMap) {
|
|
94
|
+
marker.classList.remove(CLASS_FADE)
|
|
95
|
+
marker.classList.toggle(`${CLASS_MARKER}-past`, disabled ? false : clicks < current)
|
|
96
|
+
marker.classList.toggle(`${CLASS_MARKER}-active`, disabled ? false : clicks === current)
|
|
97
|
+
marker.classList.toggle(`${CLASS_MARKER}-next`, disabled ? false : clicks === current + 1)
|
|
98
|
+
marker.classList.toggle(`${CLASS_MARKER}-future`, disabled ? false : clicks > current + 1)
|
|
99
|
+
marker.ondblclick = (e) => {
|
|
100
|
+
emit('markerDblclick', e, clicks)
|
|
101
|
+
if (e.defaultPrevented)
|
|
102
|
+
return
|
|
103
|
+
props.clicksContext!.current = clicks
|
|
104
|
+
e.stopPropagation()
|
|
105
|
+
e.stopImmediatePropagation()
|
|
106
|
+
}
|
|
107
|
+
marker.onclick = (e) => {
|
|
108
|
+
emit('markerClick', e, clicks)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (props.autoScroll && clicks === current)
|
|
112
|
+
marker.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
watch(
|
|
117
|
+
() => [props.noteHtml, props.clicksContext?.current],
|
|
118
|
+
() => {
|
|
119
|
+
nextTick(() => {
|
|
120
|
+
highlightNote()
|
|
121
|
+
})
|
|
122
|
+
},
|
|
123
|
+
{ immediate: true },
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
onMounted(() => {
|
|
127
|
+
highlightNote()
|
|
128
|
+
})
|
|
10
129
|
</script>
|
|
11
130
|
|
|
12
131
|
<template>
|
|
13
132
|
<div
|
|
14
133
|
v-if="noteHtml"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
134
|
+
ref="noteDisplay"
|
|
135
|
+
class="prose overflow-auto outline-none slidev-note"
|
|
136
|
+
:class="[props.class, withClicks ? 'slidev-note-with-clicks' : '']"
|
|
18
137
|
v-html="noteHtml"
|
|
19
138
|
/>
|
|
20
139
|
<div
|
|
21
140
|
v-else-if="note"
|
|
22
|
-
class="prose overflow-auto outline-none"
|
|
141
|
+
class="prose overflow-auto outline-none slidev-note"
|
|
23
142
|
:class="props.class"
|
|
24
|
-
@click="$emit('click')"
|
|
25
143
|
>
|
|
26
144
|
<p v-text="note" />
|
|
27
145
|
</div>
|
|
28
146
|
<div
|
|
29
147
|
v-else
|
|
30
|
-
class="prose overflow-auto outline-none opacity-50 italic"
|
|
148
|
+
class="prose overflow-auto outline-none opacity-50 italic select-none slidev-note"
|
|
31
149
|
:class="props.class"
|
|
32
|
-
@click="$emit('click')"
|
|
33
150
|
>
|
|
34
151
|
<p v-text="props.placeholder || 'No notes.'" />
|
|
35
152
|
</div>
|
|
36
153
|
</template>
|
|
154
|
+
|
|
155
|
+
<style>
|
|
156
|
+
.slidev-note :first-child {
|
|
157
|
+
margin-top: 0;
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import { nextTick, ref, watch, watchEffect } from 'vue'
|
|
4
|
+
import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
|
|
5
|
+
import type { ClicksContext } from '@slidev/types'
|
|
6
|
+
import { useDynamicSlideInfo } from '../logic/note'
|
|
7
|
+
import NoteDisplay from './NoteDisplay.vue'
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
no: {
|
|
11
|
+
type: Number,
|
|
12
|
+
},
|
|
13
|
+
class: {
|
|
14
|
+
default: '',
|
|
15
|
+
},
|
|
16
|
+
editing: {
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
style: {
|
|
20
|
+
default: () => ({}),
|
|
21
|
+
},
|
|
22
|
+
placeholder: {
|
|
23
|
+
default: 'No notes for this slide',
|
|
24
|
+
},
|
|
25
|
+
clicksContext: {
|
|
26
|
+
type: Object as PropType<ClicksContext>,
|
|
27
|
+
},
|
|
28
|
+
autoHeight: {
|
|
29
|
+
default: false,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
(type: 'update:editing', value: boolean): void
|
|
35
|
+
(type: 'markerDblclick', e: MouseEvent, clicks: number): void
|
|
36
|
+
(type: 'markerClick', e: MouseEvent, clicks: number): void
|
|
37
|
+
}>()
|
|
38
|
+
|
|
39
|
+
const editing = useVModel(props, 'editing', emit, { passive: true })
|
|
40
|
+
|
|
41
|
+
const { info, update } = useDynamicSlideInfo(props.no)
|
|
42
|
+
|
|
43
|
+
const note = ref('')
|
|
44
|
+
let timer: any
|
|
45
|
+
|
|
46
|
+
// Send back the note on changes
|
|
47
|
+
const { ignoreUpdates } = ignorableWatch(
|
|
48
|
+
note,
|
|
49
|
+
(v) => {
|
|
50
|
+
if (!editing.value)
|
|
51
|
+
return
|
|
52
|
+
const id = props.no
|
|
53
|
+
clearTimeout(timer)
|
|
54
|
+
timer = setTimeout(() => {
|
|
55
|
+
update({ note: v }, id)
|
|
56
|
+
}, 500)
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Update note value when info changes
|
|
61
|
+
watch(
|
|
62
|
+
() => info.value?.note,
|
|
63
|
+
(value = '') => {
|
|
64
|
+
if (editing.value)
|
|
65
|
+
return
|
|
66
|
+
clearTimeout(timer)
|
|
67
|
+
ignoreUpdates(() => {
|
|
68
|
+
note.value = value
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
{ immediate: true, flush: 'sync' },
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const inputEl = ref<HTMLTextAreaElement>()
|
|
75
|
+
const inputHeight = ref<number | null>()
|
|
76
|
+
|
|
77
|
+
watchEffect(() => {
|
|
78
|
+
if (editing.value)
|
|
79
|
+
inputEl.value?.focus()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
onClickOutside(inputEl, () => {
|
|
83
|
+
editing.value = false
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function calculateEditorHeight() {
|
|
87
|
+
if (!props.autoHeight || !inputEl.value || !editing.value)
|
|
88
|
+
return
|
|
89
|
+
if (inputEl.value.scrollHeight > inputEl.value.clientHeight)
|
|
90
|
+
inputEl.value.style.height = `${inputEl.value.scrollHeight}px`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
watch(
|
|
94
|
+
[note, editing],
|
|
95
|
+
() => {
|
|
96
|
+
nextTick(() => {
|
|
97
|
+
calculateEditorHeight()
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
{ flush: 'post', immediate: true },
|
|
101
|
+
)
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<NoteDisplay
|
|
106
|
+
v-if="!editing"
|
|
107
|
+
class="border-transparent border-2"
|
|
108
|
+
:class="[props.class, note ? '' : 'opacity-25 italic select-none']"
|
|
109
|
+
:style="props.style"
|
|
110
|
+
:note="note || placeholder"
|
|
111
|
+
:note-html="info?.noteHTML"
|
|
112
|
+
:clicks-context="clicksContext"
|
|
113
|
+
:auto-scroll="!autoHeight"
|
|
114
|
+
@marker-click="(e, clicks) => emit('markerClick', e, clicks)"
|
|
115
|
+
@marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
|
|
116
|
+
/>
|
|
117
|
+
<textarea
|
|
118
|
+
v-else
|
|
119
|
+
ref="inputEl"
|
|
120
|
+
v-model="note"
|
|
121
|
+
class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
|
|
122
|
+
style="line-height: 1.75;"
|
|
123
|
+
:style="[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]"
|
|
124
|
+
:class="props.class"
|
|
125
|
+
:placeholder="placeholder"
|
|
126
|
+
@keydown.esc="editing = false"
|
|
127
|
+
/>
|
|
128
|
+
</template>
|
package/internals/NoteStatic.vue
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { ClicksContext } from 'packages/types'
|
|
3
|
+
import { useSlideInfo } from '../logic/note'
|
|
4
4
|
import NoteDisplay from './NoteDisplay.vue'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
|
+
no?: number
|
|
7
8
|
class?: string
|
|
9
|
+
clicksContext?: ClicksContext
|
|
8
10
|
}>()
|
|
9
11
|
|
|
10
|
-
const
|
|
11
|
-
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
|
|
12
|
+
const { info } = useSlideInfo(props.no)
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
15
|
<template>
|
|
15
16
|
<NoteDisplay
|
|
16
17
|
:class="props.class"
|
|
17
|
-
:note="note"
|
|
18
|
-
:note-html="
|
|
18
|
+
:note="info?.note"
|
|
19
|
+
:note-html="info?.noteHTML"
|
|
20
|
+
:clicks-context="clicksContext"
|
|
19
21
|
/>
|
|
20
22
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { parseRangeString } from '@slidev/parser/core'
|
|
3
|
-
import { computed
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { provideLocal } from '@vueuse/core'
|
|
4
5
|
import { configs, slideAspect, slideWidth } from '../env'
|
|
5
6
|
import { injectionSlideScale } from '../constants'
|
|
6
7
|
import { route as currentRoute, rawRoutes } from '../logic/nav'
|
|
@@ -31,7 +32,7 @@ const className = computed(() => ({
|
|
|
31
32
|
'select-none': !configs.selectable,
|
|
32
33
|
}))
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
provideLocal(injectionSlideScale, scale)
|
|
35
36
|
</script>
|
|
36
37
|
|
|
37
38
|
<template>
|
|
@@ -49,6 +50,6 @@ provide(injectionSlideScale, scale)
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
.print-slide-container {
|
|
52
|
-
@apply relative overflow-hidden break-after-page;
|
|
53
|
+
@apply relative overflow-hidden break-after-page translate-0;
|
|
53
54
|
}
|
|
54
55
|
</style>
|
package/internals/PrintSlide.vue
CHANGED
|
@@ -9,7 +9,7 @@ const props = defineProps<{ route: RouteRecordRaw }>()
|
|
|
9
9
|
|
|
10
10
|
const route = computed(() => props.route)
|
|
11
11
|
const nav = useNav(route)
|
|
12
|
-
const clicks0 = useFixedClicks(route.value, 0)
|
|
12
|
+
const clicks0 = useFixedClicks(route.value, 0)
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
@@ -19,6 +19,12 @@ const clicks0 = useFixedClicks(route.value, 0)[1]
|
|
|
19
19
|
:route="route"
|
|
20
20
|
/>
|
|
21
21
|
<template v-if="!clicks0.disabled">
|
|
22
|
-
<PrintSlideClick
|
|
22
|
+
<PrintSlideClick
|
|
23
|
+
v-for="i of clicks0.total"
|
|
24
|
+
:key="i"
|
|
25
|
+
:clicks-context="useFixedClicks(route, i)"
|
|
26
|
+
:nav="nav"
|
|
27
|
+
:route="route"
|
|
28
|
+
/>
|
|
23
29
|
</template>
|
|
24
30
|
</template>
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
-
import { computed,
|
|
3
|
+
import { computed, reactive, shallowRef } from 'vue'
|
|
4
4
|
import type { ClicksContext } from '@slidev/types'
|
|
5
|
+
import { provideLocal } from '@vueuse/core'
|
|
5
6
|
import { injectionSlidevContext } from '../constants'
|
|
6
7
|
import { configs, slideHeight, slideWidth } from '../env'
|
|
7
8
|
import { getSlideClass } from '../utils'
|
|
8
9
|
import type { SlidevContextNav } from '../modules/context'
|
|
9
10
|
import SlideWrapper from './SlideWrapper'
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
// @ts-expect-error virtual module
|
|
15
|
-
import GlobalBottom from '/@slidev/global-components/bottom'
|
|
12
|
+
import GlobalTop from '#slidev/global-components/top'
|
|
13
|
+
import GlobalBottom from '#slidev/global-components/bottom'
|
|
16
14
|
|
|
17
15
|
const props = defineProps<{
|
|
18
16
|
clicksContext: ClicksContext
|
|
@@ -33,7 +31,7 @@ const id = computed(() =>
|
|
|
33
31
|
`${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
|
|
34
32
|
)
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
provideLocal(injectionSlidevContext, reactive({
|
|
37
35
|
nav: props.nav,
|
|
38
36
|
configs,
|
|
39
37
|
themeConfigs: computed(() => configs.themeConfig),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useEventListener, useVModel } from '@vueuse/core'
|
|
3
3
|
import { computed, ref, watchEffect } from 'vue'
|
|
4
|
-
import { themeVars } from '../env'
|
|
5
4
|
import { breakpoints, showOverview, windowSize } from '../state'
|
|
6
5
|
import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
|
|
7
6
|
import { currentOverviewPage, overviewRowCount } from '../logic/overview'
|
|
8
7
|
import { useFixedClicks } from '../composables/useClicks'
|
|
9
8
|
import { getSlideClass } from '../utils'
|
|
9
|
+
import { CLICKS_MAX } from '../constants'
|
|
10
10
|
import SlideContainer from './SlideContainer.vue'
|
|
11
11
|
import SlideWrapper from './SlideWrapper'
|
|
12
12
|
import DrawingPreview from './DrawingPreview.vue'
|
|
@@ -14,7 +14,7 @@ import IconButton from './IconButton.vue'
|
|
|
14
14
|
|
|
15
15
|
const props = defineProps<{ modelValue: boolean }>()
|
|
16
16
|
|
|
17
|
-
const emit = defineEmits([])
|
|
17
|
+
const emit = defineEmits(['update:modelValue'])
|
|
18
18
|
const value = useVModel(props, 'modelValue', emit)
|
|
19
19
|
|
|
20
20
|
function close() {
|
|
@@ -112,7 +112,7 @@ watchEffect(() => {
|
|
|
112
112
|
>
|
|
113
113
|
<div
|
|
114
114
|
v-show="value"
|
|
115
|
-
class="bg-main !bg-opacity-75 p-16 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
|
|
115
|
+
class="bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
|
|
116
116
|
@click="close()"
|
|
117
117
|
>
|
|
118
118
|
<div
|
|
@@ -125,9 +125,8 @@ watchEffect(() => {
|
|
|
125
125
|
class="relative"
|
|
126
126
|
>
|
|
127
127
|
<div
|
|
128
|
-
class="inline-block border rounded
|
|
129
|
-
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border
|
|
130
|
-
:style="themeVars"
|
|
128
|
+
class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
|
|
129
|
+
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
|
|
131
130
|
@click="go(+route.path)"
|
|
132
131
|
>
|
|
133
132
|
<SlideContainer
|
|
@@ -139,7 +138,7 @@ watchEffect(() => {
|
|
|
139
138
|
<SlideWrapper
|
|
140
139
|
:is="route.component"
|
|
141
140
|
v-if="route?.component"
|
|
142
|
-
:clicks-context="useFixedClicks(route,
|
|
141
|
+
:clicks-context="useFixedClicks(route, CLICKS_MAX)"
|
|
143
142
|
:class="getSlideClass(route)"
|
|
144
143
|
:route="route"
|
|
145
144
|
render-context="overview"
|
|
@@ -163,7 +162,19 @@ watchEffect(() => {
|
|
|
163
162
|
</div>
|
|
164
163
|
</div>
|
|
165
164
|
</Transition>
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
165
|
+
<div v-if="value" class="fixed top-4 right-4 text-gray-400 flex flex-col items-center gap-2">
|
|
166
|
+
<IconButton title="Close" class="text-2xl" @click="close">
|
|
167
|
+
<carbon:close />
|
|
168
|
+
</IconButton>
|
|
169
|
+
<IconButton
|
|
170
|
+
as="a"
|
|
171
|
+
title="Slides Overview"
|
|
172
|
+
target="_blank"
|
|
173
|
+
href="/overview"
|
|
174
|
+
tab-index="-1"
|
|
175
|
+
class="text-2xl"
|
|
176
|
+
>
|
|
177
|
+
<carbon:list-boxes />
|
|
178
|
+
</IconButton>
|
|
179
|
+
</div>
|
|
169
180
|
</template>
|
|
@@ -54,7 +54,7 @@ onMounted(() => {
|
|
|
54
54
|
</IconButton>
|
|
55
55
|
<MenuButton :disabled="recording">
|
|
56
56
|
<template #button>
|
|
57
|
-
<IconButton title="Select recording device" class="h-full !text-sm !px-0">
|
|
57
|
+
<IconButton title="Select recording device" class="h-full !text-sm !px-0 aspect-initial">
|
|
58
58
|
<carbon:chevron-up class="opacity-50" />
|
|
59
59
|
</IconButton>
|
|
60
60
|
</template>
|
|
@@ -108,19 +108,18 @@ async function start() {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
input[type=
|
|
112
|
-
@apply border border-
|
|
111
|
+
input[type='text'] {
|
|
112
|
+
@apply border border-main rounded px-2 py-1;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
button {
|
|
116
116
|
@apply bg-orange-400 text-white px-4 py-1 rounded border-b-2 border-orange-600;
|
|
117
|
-
@apply hover:(bg-orange-500 border-orange-700)
|
|
117
|
+
@apply hover:(bg-orange-500 border-orange-700);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
button.cancel {
|
|
121
|
-
@apply bg-gray-400 text-white px-4 py-1 rounded border-b-2 border-
|
|
122
|
-
@apply bg-opacity-
|
|
123
|
-
@apply hover:(bg-opacity-75 border-opacity-75)
|
|
121
|
+
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
|
|
122
|
+
@apply hover:(bg-opacity-75 border-opacity-75);
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
</style>
|
|
@@ -38,7 +38,6 @@ watch(
|
|
|
38
38
|
async function save() {
|
|
39
39
|
dirty.value = false
|
|
40
40
|
await update({
|
|
41
|
-
raw: null!,
|
|
42
41
|
note: note.value || undefined,
|
|
43
42
|
content: content.value,
|
|
44
43
|
// frontmatter: frontmatter.value,
|
|
@@ -103,6 +102,11 @@ onMounted(async () => {
|
|
|
103
102
|
noteEditor.refresh()
|
|
104
103
|
})
|
|
105
104
|
})
|
|
105
|
+
|
|
106
|
+
watch(currentSlideId, () => {
|
|
107
|
+
contentEditor.clearHistory()
|
|
108
|
+
noteEditor.clearHistory()
|
|
109
|
+
}, { flush: 'post' })
|
|
106
110
|
})
|
|
107
111
|
|
|
108
112
|
const handlerDown = ref(false)
|
|
@@ -168,13 +172,13 @@ throttledWatch(
|
|
|
168
172
|
<div class="flex pb-2 text-xl -mt-1">
|
|
169
173
|
<div class="mr-4 rounded flex">
|
|
170
174
|
<IconButton
|
|
171
|
-
title="Switch to content tab" :class="tab === 'content' ? 'text
|
|
175
|
+
title="Switch to content tab" :class="tab === 'content' ? 'text-primary' : ''"
|
|
172
176
|
@click="switchTab('content')"
|
|
173
177
|
>
|
|
174
178
|
<carbon:account />
|
|
175
179
|
</IconButton>
|
|
176
180
|
<IconButton
|
|
177
|
-
title="Switch to notes tab" :class="tab === 'note' ? 'text
|
|
181
|
+
title="Switch to notes tab" :class="tab === 'note' ? 'text-primary' : ''"
|
|
178
182
|
@click="switchTab('note')"
|
|
179
183
|
>
|
|
180
184
|
<carbon:align-box-bottom-right />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useElementSize, useStyleTag } from '@vueuse/core'
|
|
3
|
-
import { computed,
|
|
2
|
+
import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
|
|
3
|
+
import { computed, ref, watchEffect } from 'vue'
|
|
4
4
|
import { configs, slideAspect, slideHeight, slideWidth } from '../env'
|
|
5
5
|
import { injectionSlideScale } from '../constants'
|
|
6
|
-
import { isPrintMode } from '../logic/nav'
|
|
6
|
+
import { clicksDirection, isPrintMode } from '../logic/nav'
|
|
7
7
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
width: {
|
|
@@ -47,13 +47,16 @@ const scale = computed(() => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
const style = computed(() => ({
|
|
50
|
-
height: `${slideHeight}px`,
|
|
51
|
-
width: `${slideWidth}px`,
|
|
52
|
-
transform: `translate(-50%, -50%) scale(${scale.value})`,
|
|
50
|
+
'height': `${slideHeight}px`,
|
|
51
|
+
'width': `${slideWidth}px`,
|
|
52
|
+
'transform': `translate(-50%, -50%) scale(${scale.value})`,
|
|
53
|
+
'--slidev-slide-scale': scale.value,
|
|
53
54
|
}))
|
|
54
55
|
|
|
55
56
|
const className = computed(() => ({
|
|
56
57
|
'select-none': !configs.selectable,
|
|
58
|
+
'slidev-nav-go-forward': clicksDirection.value > 0,
|
|
59
|
+
'slidev-nav-go-backward': clicksDirection.value < 0,
|
|
57
60
|
}))
|
|
58
61
|
|
|
59
62
|
if (props.isMain) {
|
|
@@ -64,12 +67,12 @@ if (props.isMain) {
|
|
|
64
67
|
`))
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
provideLocal(injectionSlideScale, scale as any)
|
|
68
71
|
</script>
|
|
69
72
|
|
|
70
73
|
<template>
|
|
71
|
-
<div id="slide-container" ref="root" :class="className">
|
|
72
|
-
<div id="slide-content" :style="style">
|
|
74
|
+
<div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
|
|
75
|
+
<div id="slide-content" class="slidev-slide-content" :style="style">
|
|
73
76
|
<slot />
|
|
74
77
|
</div>
|
|
75
78
|
<slot name="controls" />
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { defineComponent, h,
|
|
1
|
+
import { computed, defineComponent, h, ref, toRef } from 'vue'
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
|
+
import { provideLocal } from '@vueuse/core'
|
|
3
4
|
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
5
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
4
6
|
import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
|
|
5
7
|
|
|
6
8
|
export default defineComponent({
|
|
@@ -20,23 +22,37 @@ export default defineComponent({
|
|
|
20
22
|
},
|
|
21
23
|
is: {
|
|
22
24
|
type: Object,
|
|
23
|
-
|
|
25
|
+
required: true,
|
|
24
26
|
},
|
|
25
27
|
route: {
|
|
26
|
-
type: Object
|
|
27
|
-
|
|
28
|
+
type: Object as PropType<RouteRecordRaw>,
|
|
29
|
+
required: true,
|
|
28
30
|
},
|
|
29
31
|
},
|
|
30
32
|
setup(props) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
provideLocal(injectionRoute, props.route)
|
|
34
|
+
provideLocal(injectionCurrentPage, ref(+props.route.path))
|
|
35
|
+
provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
|
|
36
|
+
provideLocal(injectionActive, toRef(props, 'active'))
|
|
37
|
+
provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
|
|
38
|
+
|
|
39
|
+
const style = computed(() => {
|
|
40
|
+
const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
|
|
41
|
+
return zoom === 1
|
|
42
|
+
? undefined
|
|
43
|
+
: {
|
|
44
|
+
width: `${100 / zoom}%`,
|
|
45
|
+
height: `${100 / zoom}%`,
|
|
46
|
+
transformOrigin: 'top left',
|
|
47
|
+
transform: `scale(${zoom})`,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
style,
|
|
53
|
+
}
|
|
36
54
|
},
|
|
37
55
|
render() {
|
|
38
|
-
|
|
39
|
-
return h(this.$props.is)
|
|
40
|
-
return this.$slots?.default?.()
|
|
56
|
+
return h(this.$props.is, { style: this.style })
|
|
41
57
|
},
|
|
42
58
|
})
|