@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.
Files changed (78) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +14 -6
  4. package/builtin/KaTexBlockWrapper.vue +5 -4
  5. package/builtin/Mermaid.vue +4 -3
  6. package/builtin/Monaco.vue +109 -92
  7. package/builtin/RenderWhen.vue +3 -3
  8. package/builtin/ShikiMagicMove.vue +50 -0
  9. package/builtin/SlideCurrentNo.vue +2 -3
  10. package/builtin/SlidesTotal.vue +3 -4
  11. package/builtin/SlidevVideo.vue +8 -6
  12. package/builtin/Toc.vue +3 -3
  13. package/builtin/TocList.vue +3 -2
  14. package/builtin/Tweet.vue +3 -22
  15. package/builtin/VClick.ts +2 -1
  16. package/builtin/VClickGap.vue +3 -5
  17. package/builtin/VClicks.ts +1 -1
  18. package/composables/useClicks.ts +34 -16
  19. package/constants.ts +58 -8
  20. package/context.ts +73 -0
  21. package/env.ts +3 -12
  22. package/internals/ClicksSlider.vue +93 -0
  23. package/internals/Controls.vue +2 -2
  24. package/internals/DrawingControls.vue +39 -9
  25. package/internals/DrawingLayer.vue +3 -3
  26. package/internals/Goto.vue +5 -4
  27. package/internals/IconButton.vue +7 -3
  28. package/internals/InfoDialog.vue +1 -1
  29. package/internals/Modal.vue +1 -1
  30. package/internals/NavControls.vue +4 -5
  31. package/internals/NoteDisplay.vue +131 -8
  32. package/internals/NoteEditable.vue +128 -0
  33. package/internals/NoteStatic.vue +8 -6
  34. package/internals/PrintContainer.vue +4 -3
  35. package/internals/PrintSlide.vue +8 -2
  36. package/internals/PrintSlideClick.vue +5 -7
  37. package/internals/{SlidesOverview.vue → QuickOverview.vue} +21 -10
  38. package/internals/RecordingControls.vue +1 -1
  39. package/internals/RecordingDialog.vue +5 -6
  40. package/internals/{Editor.vue → SideEditor.vue} +7 -3
  41. package/internals/SlideContainer.vue +12 -9
  42. package/internals/SlideWrapper.ts +28 -12
  43. package/internals/SlidesShow.vue +7 -8
  44. package/layouts/two-cols-header.vue +9 -3
  45. package/logic/drawings.ts +6 -3
  46. package/logic/nav.ts +11 -8
  47. package/logic/note.ts +7 -7
  48. package/main.ts +8 -4
  49. package/modules/context.ts +4 -3
  50. package/modules/mermaid.ts +6 -7
  51. package/modules/{directives.ts → v-click.ts} +15 -15
  52. package/modules/v-mark.ts +159 -0
  53. package/package.json +26 -16
  54. package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
  55. package/{internals/NotesView.vue → pages/notes.vue} +5 -3
  56. package/pages/overview.vue +229 -0
  57. package/{internals/Play.vue → pages/play.vue} +15 -12
  58. package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +12 -7
  59. package/{internals/Presenter.vue → pages/presenter.vue} +108 -100
  60. package/{internals/Print.vue → pages/print.vue} +3 -4
  61. package/routes.ts +27 -51
  62. package/setup/codemirror.ts +8 -3
  63. package/setup/monaco.ts +108 -44
  64. package/setup/root.ts +2 -2
  65. package/shim-vue.d.ts +35 -0
  66. package/shim.d.ts +1 -13
  67. package/state/index.ts +10 -10
  68. package/styles/code.css +7 -3
  69. package/styles/index.css +68 -7
  70. package/styles/katex.css +1 -1
  71. package/styles/layouts-base.css +17 -12
  72. package/styles/monaco.css +27 -0
  73. package/styles/vars.css +1 -0
  74. package/uno.config.ts +14 -2
  75. package/iframes/monaco/index.css +0 -28
  76. package/iframes/monaco/index.html +0 -7
  77. package/iframes/monaco/index.ts +0 -260
  78. 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
- defineEmits(['click'])
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
- class="prose overflow-auto outline-none"
16
- :class="props.class"
17
- @click="$emit('click')"
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>
@@ -1,20 +1,22 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { currentRoute } from '../logic/nav'
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 note = computed(() => currentRoute.value?.meta?.slide?.note)
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="noteHtml"
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, provide } from 'vue'
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
- provide(injectionSlideScale, scale)
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>
@@ -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)[1]
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 v-for="i of clicks0.total" :key="i" :clicks-context="useFixedClicks(route, i)[1]" :nav="nav" :route="route" />
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, provide, reactive, shallowRef } from 'vue'
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
- // @ts-expect-error virtual module
12
- import GlobalTop from '/@slidev/global-components/top'
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
- provide(injectionSlidevContext, reactive({
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 border-opacity-50 overflow-hidden bg-main hover:border-$slidev-theme-primary transition"
129
- :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-$slidev-theme-primary' : 'border-gray-400'"
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, 99999)[1]"
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
- <IconButton v-if="value" title="Close" class="fixed text-2xl top-4 right-4 text-gray-400" @click="close">
167
- <carbon:close />
168
- </IconButton>
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="text"] {
112
- @apply border border-gray-400 rounded px-2 py-1;
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-gray-500;
122
- @apply bg-opacity-50 border-opacity-50;
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-$slidev-theme-primary' : ''"
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-$slidev-theme-primary' : ''"
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, provide, ref, watchEffect } from 'vue'
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
- provide(injectionSlideScale, scale as any)
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, provide, ref, toRef } from 'vue'
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
- default: undefined,
25
+ required: true,
24
26
  },
25
27
  route: {
26
- type: Object,
27
- default: undefined,
28
+ type: Object as PropType<RouteRecordRaw>,
29
+ required: true,
28
30
  },
29
31
  },
30
32
  setup(props) {
31
- provide(injectionRoute, props.route as any)
32
- provide(injectionCurrentPage, ref(+props.route?.path))
33
- provide(injectionRenderContext, ref(props.renderContext as RenderContext))
34
- provide(injectionActive, toRef(props, 'active'))
35
- provide(injectionClicksContext, toRef(props, 'clicksContext'))
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
- if (this.$props.is)
39
- return h(this.$props.is)
40
- return this.$slots?.default?.()
56
+ return h(this.$props.is, { style: this.style })
41
57
  },
42
58
  })