@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
@@ -0,0 +1,229 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
3
+ import { useHead } from '@unhead/vue'
4
+ import type { RouteRecordRaw } from 'vue-router'
5
+ import type { ClicksContext } from 'packages/types'
6
+ import { configs } from '../env'
7
+ import { openInEditor, rawRoutes } from '../logic/nav'
8
+ import { useFixedClicks } from '../composables/useClicks'
9
+ import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
10
+ import { getSlideClass } from '../utils'
11
+ import SlideContainer from '../internals/SlideContainer.vue'
12
+ import SlideWrapper from '../internals/SlideWrapper'
13
+ import DrawingPreview from '../internals/DrawingPreview.vue'
14
+ import IconButton from '../internals/IconButton.vue'
15
+ import NoteEditable from '../internals/NoteEditable.vue'
16
+ import ClicksSlider from '../internals/ClicksSlider.vue'
17
+ import { CLICKS_MAX } from '../constants'
18
+
19
+ const cardWidth = 450
20
+
21
+ const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
22
+ useHead({
23
+ title: `Overview - ${slideTitle}`,
24
+ })
25
+
26
+ const blocks: Map<number, HTMLElement> = reactive(new Map())
27
+ const activeBlocks = ref<number[]>([])
28
+ const edittingNote = ref<number | null>(null)
29
+ const wordCounts = computed(() => rawRoutes.map(route => wordCount(route.meta?.slide?.note || '')))
30
+ const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
31
+ const totalClicks = computed(() => rawRoutes.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
32
+
33
+ const clicksContextMap = new WeakMap<RouteRecordRaw, ClicksContext>()
34
+ function getClicksContext(route: RouteRecordRaw) {
35
+ // We create a local clicks context to calculate the total clicks of the slide
36
+ if (!clicksContextMap.has(route))
37
+ clicksContextMap.set(route, useFixedClicks(route, CLICKS_MAX))
38
+ return clicksContextMap.get(route)!
39
+ }
40
+
41
+ function getSlideClicks(route: RouteRecordRaw) {
42
+ return route.meta?.clicks || getClicksContext(route)?.total
43
+ }
44
+
45
+ function wordCount(str: string) {
46
+ return str.match(/[\w\d\’\'-]+/gi)?.length || 0
47
+ }
48
+
49
+ function isElementInViewport(el: HTMLElement) {
50
+ const rect = el.getBoundingClientRect()
51
+ const delta = 20
52
+ return (
53
+ rect.top >= 0 - delta
54
+ && rect.left >= 0 - delta
55
+ && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + delta
56
+ && rect.right <= (window.innerWidth || document.documentElement.clientWidth) + delta
57
+ )
58
+ }
59
+
60
+ function checkActiveBlocks() {
61
+ const active: number[] = []
62
+ Array.from(blocks.entries())
63
+ .forEach(([idx, el]) => {
64
+ if (isElementInViewport(el))
65
+ active.push(idx)
66
+ })
67
+ activeBlocks.value = active
68
+ }
69
+
70
+ function openSlideInNewTab(path: string) {
71
+ const a = document.createElement('a')
72
+ a.target = '_blank'
73
+ a.href = path
74
+ a.click()
75
+ }
76
+
77
+ function scrollToSlide(idx: number) {
78
+ const el = blocks.get(idx)
79
+ if (el)
80
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' })
81
+ }
82
+
83
+ function onMarkerClick(e: MouseEvent, clicks: number, route: RouteRecordRaw) {
84
+ const ctx = getClicksContext(route)
85
+ if (ctx.current === clicks)
86
+ ctx.current = CLICKS_MAX
87
+ else
88
+ ctx.current = clicks
89
+ e.preventDefault()
90
+ }
91
+
92
+ onMounted(() => {
93
+ nextTick(() => {
94
+ checkActiveBlocks()
95
+ })
96
+ })
97
+ </script>
98
+
99
+ <template>
100
+ <div class="h-screen w-screen of-hidden flex">
101
+ <nav class="h-full flex flex-col border-r border-main p2 select-none">
102
+ <div class="flex flex-col flex-auto items-center justify-center group gap-1">
103
+ <div
104
+ v-for="(route, idx) of rawRoutes"
105
+ :key="route.path"
106
+ class="relative"
107
+ >
108
+ <button
109
+ class="relative transition duration-300 w-8 h-8 rounded hover:bg-active hover:op100"
110
+ :class="activeBlocks.includes(idx) ? 'op100 text-primary bg-gray:5' : 'op20'"
111
+ @click="scrollToSlide(idx)"
112
+ >
113
+ <div>{{ idx + 1 }}</div>
114
+ </button>
115
+ <div
116
+ v-if="route.meta?.slide?.title"
117
+ class="pointer-events-none select-none absolute left-110% bg-main top-50% translate-y--50% ws-nowrap z-10 px2 shadow-xl rounded border border-main transition duration-400 op0 group-hover:op100"
118
+ :class="activeBlocks.includes(idx) ? 'text-primary' : 'text-main important-text-op-50'"
119
+ >
120
+ {{ route.meta?.slide?.title }}
121
+ </div>
122
+ </div>
123
+ </div>
124
+ <IconButton
125
+ v-if="!isColorSchemaConfigured"
126
+ :title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
127
+ @click="toggleDark()"
128
+ >
129
+ <carbon-moon v-if="isDark" />
130
+ <carbon-sun v-else />
131
+ </IconButton>
132
+ </nav>
133
+ <main
134
+ class="flex-1 h-full of-auto"
135
+ :style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
136
+ @scroll="checkActiveBlocks"
137
+ >
138
+ <div
139
+ v-for="(route, idx) of rawRoutes"
140
+ :key="route.path"
141
+ :ref="el => blocks.set(idx, el as any)"
142
+ class="relative border-t border-main of-hidden flex gap-4 min-h-50 group"
143
+ >
144
+ <div class="select-none w-13 text-right my4 flex flex-col gap-1 items-end">
145
+ <div class="text-3xl op20 mb2">
146
+ {{ idx + 1 }}
147
+ </div>
148
+ <IconButton
149
+ class="mr--3 op0 group-hover:op80"
150
+ title="Play in new tab"
151
+ @click="openSlideInNewTab(route.path)"
152
+ >
153
+ <carbon:presentation-file />
154
+ </IconButton>
155
+ <IconButton
156
+ v-if="route.meta?.slide"
157
+ class="mr--3 op0 group-hover:op80"
158
+ title="Open in editor"
159
+ @click="openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
160
+ >
161
+ <carbon:cics-program />
162
+ </IconButton>
163
+ </div>
164
+ <div class="flex flex-col gap-2 my5">
165
+ <div
166
+ class="border rounded border-main overflow-hidden bg-main select-none h-max"
167
+ @dblclick="openSlideInNewTab(route.path)"
168
+ >
169
+ <SlideContainer
170
+ :key="route.path"
171
+ :width="cardWidth"
172
+ :clicks-disabled="true"
173
+ class="pointer-events-none important:[&_*]:select-none"
174
+ >
175
+ <SlideWrapper
176
+ :is="route.component"
177
+ v-if="route?.component"
178
+ :clicks-context="getClicksContext(route)"
179
+ :class="getSlideClass(route)"
180
+ :route="route"
181
+ render-context="overview"
182
+ />
183
+ <DrawingPreview :page="+route.path" />
184
+ </SlideContainer>
185
+ </div>
186
+ <ClicksSlider
187
+ v-if="getSlideClicks(route)"
188
+ mt-2
189
+ :clicks-context="getClicksContext(route)"
190
+ class="w-full"
191
+ />
192
+ </div>
193
+ <div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
194
+ <IconButton
195
+ title="Edit Note"
196
+ class="rounded-full w-9 h-9 text-sm"
197
+ :class="edittingNote === idx ? 'important:op0' : ''"
198
+ @click="edittingNote = idx"
199
+ >
200
+ <carbon:pen />
201
+ </IconButton>
202
+ </div>
203
+ <NoteEditable
204
+ :no="idx"
205
+ class="max-w-250 w-250 text-lg rounded p3"
206
+ :auto-height="true"
207
+ :editing="edittingNote === idx"
208
+ :clicks-context="getClicksContext(route)"
209
+ @dblclick="edittingNote !== idx ? edittingNote = idx : null"
210
+ @update:editing="edittingNote = null"
211
+ @marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
212
+ />
213
+ <div
214
+ v-if="wordCounts[idx] > 0"
215
+ class="select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
216
+ >
217
+ {{ wordCounts[idx] }} words
218
+ </div>
219
+ </div>
220
+ </main>
221
+ <div class="absolute top-0 right-0 px3 py1.5 border-b border-l rounded-lb bg-main border-main select-none">
222
+ <div class="text-xs op50">
223
+ {{ rawRoutes.length }} slides ·
224
+ {{ totalClicks + rawRoutes.length - 1 }} clicks ·
225
+ {{ totalWords }} words
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </template>
@@ -4,12 +4,12 @@ import { isEditorVertical, isScreenVertical, showEditor, slideScale, windowSize
4
4
  import { isEmbedded, isPrintMode, next, prev, useSwipeControls } from '../logic/nav'
5
5
  import { isDrawing } from '../logic/drawings'
6
6
  import { registerShortcuts } from '../logic/shortcuts'
7
- import { configs, themeVars } from '../env'
8
- import Controls from './Controls.vue'
9
- import SlideContainer from './SlideContainer.vue'
10
- import NavControls from './NavControls.vue'
11
- import SlidesShow from './SlidesShow.vue'
12
- import PrintStyle from './PrintStyle.vue'
7
+ import { configs } from '../env'
8
+ import Controls from '../internals/Controls.vue'
9
+ import SlideContainer from '../internals/SlideContainer.vue'
10
+ import NavControls from '../internals/NavControls.vue'
11
+ import SlidesShow from '../internals/SlidesShow.vue'
12
+ import PrintStyle from '../internals/PrintStyle.vue'
13
13
 
14
14
  registerShortcuts()
15
15
 
@@ -31,18 +31,21 @@ useSwipeControls(root)
31
31
 
32
32
  const persistNav = computed(() => isScreenVertical.value || showEditor.value)
33
33
 
34
- const Editor = shallowRef<any>()
34
+ const SideEditor = shallowRef<any>()
35
35
  if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
36
- import('./Editor.vue').then(v => Editor.value = v.default)
36
+ import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
37
37
 
38
38
  const DrawingControls = shallowRef<any>()
39
39
  if (__SLIDEV_FEATURE_DRAWINGS__)
40
- import('./DrawingControls.vue').then(v => DrawingControls.value = v.default)
40
+ import('../internals/DrawingControls.vue').then(v => DrawingControls.value = v.default)
41
41
  </script>
42
42
 
43
43
  <template>
44
44
  <PrintStyle v-if="isPrintMode" />
45
- <div id="page-root" ref="root" class="grid" :class="isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'" :style="themeVars">
45
+ <div
46
+ id="page-root" ref="root" class="grid"
47
+ :class="isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'"
48
+ >
46
49
  <SlideContainer
47
50
  class="w-full h-full"
48
51
  :style="{ background: 'var(--slidev-slide-container-background, black)' }"
@@ -70,8 +73,8 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
70
73
  </template>
71
74
  </SlideContainer>
72
75
 
73
- <template v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && Editor && showEditor">
74
- <Editor :resize="true" />
76
+ <template v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && SideEditor && showEditor">
77
+ <SideEditor :resize="true" />
75
78
  </template>
76
79
  </div>
77
80
  <Controls />
@@ -2,9 +2,9 @@
2
2
  import { computed } from 'vue'
3
3
  import { useStyleTag } from '@vueuse/core'
4
4
  import { useHead } from '@unhead/vue'
5
- import { configs, themeVars } from '../env'
6
- import { rawRoutes, total } from '../logic/nav'
7
- import NoteDisplay from './NoteDisplay.vue'
5
+ import { configs } from '../../env'
6
+ import { rawRoutes, total } from '../../logic/nav'
7
+ import NoteDisplay from '../../internals/NoteDisplay.vue'
8
8
 
9
9
  useStyleTag(`
10
10
  @page {
@@ -24,7 +24,9 @@ html #page-root {
24
24
  }
25
25
  `)
26
26
 
27
- useHead({ title: `Notes - ${configs.title}` })
27
+ useHead({
28
+ title: `Notes - ${configs.title}`,
29
+ })
28
30
 
29
31
  const slidesWithNote = computed(() => rawRoutes
30
32
  .map(route => route.meta?.slide)
@@ -32,7 +34,7 @@ const slidesWithNote = computed(() => rawRoutes
32
34
  </script>
33
35
 
34
36
  <template>
35
- <div id="page-root" :style="themeVars">
37
+ <div id="page-root">
36
38
  <div class="m-4">
37
39
  <div class="mb-10">
38
40
  <h1 class="text-4xl font-bold mt-2">
@@ -54,9 +56,12 @@ const slidesWithNote = computed(() => rawRoutes
54
56
  <div class="flex-auto" />
55
57
  </div>
56
58
  </h2>
57
- <NoteDisplay :note-html="slide!.noteHTML" class="max-w-full" />
59
+ <NoteDisplay
60
+ :note-html="slide!.noteHTML"
61
+ class="max-w-full"
62
+ />
58
63
  </div>
59
- <hr v-if="index < slidesWithNote.length - 1" class="border-gray-400/50 mb-8">
64
+ <hr v-if="index < slidesWithNote.length - 1" class="border-main mb-8">
60
65
  </div>
61
66
  </div>
62
67
  </div>
@@ -2,25 +2,26 @@
2
2
  import { useHead } from '@unhead/vue'
3
3
  import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
4
4
  import { useMouse, useWindowFocus } from '@vueuse/core'
5
- import { clicksContext, currentPage, currentRoute, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
5
+ import { clicksContext, currentPage, currentRoute, currentSlideId, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
6
6
  import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
7
- import { configs, themeVars } from '../env'
7
+ import { configs } from '../env'
8
8
  import { sharedState } from '../state/shared'
9
9
  import { registerShortcuts } from '../logic/shortcuts'
10
10
  import { getSlideClass } from '../utils'
11
11
  import { useTimer } from '../logic/utils'
12
12
  import { isDrawing } from '../logic/drawings'
13
- import { useFixedClicks } from '../composables/useClicks'
14
- import SlideContainer from './SlideContainer.vue'
15
- import NavControls from './NavControls.vue'
16
- import SlidesOverview from './SlidesOverview.vue'
17
- import NoteEditor from './NoteEditor.vue'
18
- import NoteStatic from './NoteStatic.vue'
19
- import Goto from './Goto.vue'
20
- import SlidesShow from './SlidesShow.vue'
21
- import SlideWrapper from './SlideWrapper'
22
- import DrawingControls from './DrawingControls.vue'
23
- import IconButton from './IconButton.vue'
13
+ import { useFixedClicks, usePrimaryClicks } from '../composables/useClicks'
14
+ import SlideWrapper from '../internals/SlideWrapper'
15
+ import SlideContainer from '../internals/SlideContainer.vue'
16
+ import NavControls from '../internals/NavControls.vue'
17
+ import QuickOverview from '../internals/QuickOverview.vue'
18
+ import NoteEditable from '../internals/NoteEditable.vue'
19
+ import NoteStatic from '../internals/NoteStatic.vue'
20
+ import Goto from '../internals/Goto.vue'
21
+ import SlidesShow from '../internals/SlidesShow.vue'
22
+ import DrawingControls from '../internals/DrawingControls.vue'
23
+ import IconButton from '../internals/IconButton.vue'
24
+ import ClicksSlider from '../internals/ClicksSlider.vue'
24
25
 
25
26
  const main = ref<HTMLDivElement>()
26
27
 
@@ -45,16 +46,23 @@ const nextFrame = computed(() => {
45
46
  else
46
47
  return null
47
48
  })
49
+
48
50
  const nextFrameClicksCtx = computed(() => {
49
51
  return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
50
52
  })
51
- watch([currentRoute, queryClicks], () => {
52
- nextFrameClicksCtx.value && (nextFrameClicksCtx.value[0].value = nextFrame.value![1])
53
- }, { immediate: true })
54
53
 
55
- const Editor = shallowRef<any>()
54
+ watch(
55
+ [currentRoute, queryClicks],
56
+ () => {
57
+ if (nextFrameClicksCtx.value)
58
+ nextFrameClicksCtx.value.current = nextFrame.value![1]
59
+ },
60
+ { immediate: true },
61
+ )
62
+
63
+ const SideEditor = shallowRef<any>()
56
64
  if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
57
- import('./Editor.vue').then(v => Editor.value = v.default)
65
+ import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
58
66
 
59
67
  // sync presenter cursor
60
68
  onMounted(() => {
@@ -86,68 +94,64 @@ onMounted(() => {
86
94
  <template>
87
95
  <div class="bg-main h-full slidev-presenter">
88
96
  <div class="grid-container" :class="`layout${presenterLayout}`">
89
- <div class="grid-section top flex">
90
- <img src="../assets/logo-title-horizontal.png" class="ml-2 my-auto h-10 py-1 lg:h-14 lg:py-2" style="height: 3.5rem;" alt="Slidev logo">
91
- <div class="flex-auto" />
92
- <div
93
- class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
94
- opacity="50 hover:100"
95
- @click="resetTimer"
96
- >
97
- <carbon:time class="absolute" />
98
- <carbon:renew class="absolute opacity-0" />
99
- </div>
100
- <div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
101
- {{ timer }}
102
- </div>
103
- </div>
104
- <div ref="main" class="relative grid-section main flex flex-col p-2 lg:p-4" :style="themeVars">
97
+ <div ref="main" class="relative grid-section main flex flex-col">
105
98
  <SlideContainer
106
99
  key="main"
107
- class="h-full w-full"
100
+ class="h-full w-full p-2 lg:p-4 flex-auto"
108
101
  >
109
102
  <template #default>
110
103
  <SlidesShow render-context="presenter" />
111
104
  </template>
112
105
  </SlideContainer>
113
- <div class="context">
114
- current
106
+ <ClicksSlider
107
+ :key="currentRoute?.path"
108
+ :clicks-context="usePrimaryClicks(currentRoute)"
109
+ class="w-full pb2 px4 flex-none"
110
+ />
111
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
112
+ Current
115
113
  </div>
116
114
  </div>
117
- <div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
115
+ <div class="relative grid-section next flex flex-col p-2 lg:p-4">
118
116
  <SlideContainer
119
117
  v-if="nextFrame && nextFrameClicksCtx"
120
118
  key="next"
121
119
  class="h-full w-full"
122
120
  >
123
121
  <SlideWrapper
124
- :is="nextFrame[0].component as any"
122
+ :is="(nextFrame[0].component as any)"
125
123
  :key="nextFrame[0].path"
126
- :clicks-context="nextFrameClicksCtx[1]"
124
+ :clicks-context="nextFrameClicksCtx"
127
125
  :class="getSlideClass(nextFrame[0])"
128
126
  :route="nextFrame[0]"
129
127
  render-context="previewNext"
130
128
  />
131
129
  </SlideContainer>
132
- <div class="context">
133
- next
130
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
131
+ Next
134
132
  </div>
135
133
  </div>
136
134
  <!-- Notes -->
137
- <div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && Editor && showEditor" class="grid-section note of-auto">
138
- <Editor />
135
+ <div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && SideEditor && showEditor" class="grid-section note of-auto">
136
+ <SideEditor />
139
137
  </div>
140
138
  <div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
141
- <NoteEditor
139
+ <NoteEditable
142
140
  v-if="__DEV__"
141
+ :key="`edit-${currentSlideId}`"
142
+ v-model:editing="notesEditing"
143
+ :no="currentSlideId"
143
144
  class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
144
- :editing="notesEditing"
145
+ :clicks-context="clicksContext"
145
146
  :style="{ fontSize: `${presenterNotesFontSize}em` }"
146
147
  />
147
148
  <NoteStatic
148
149
  v-else
150
+ :key="`static-${currentSlideId}`"
151
+ :no="currentSlideId"
149
152
  class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
150
153
  :style="{ fontSize: `${presenterNotesFontSize}em` }"
154
+ :clicks-context="clicksContext"
151
155
  />
152
156
  <div class="border-t border-main py-1 px-2 text-sm">
153
157
  <IconButton title="Increase font size" @click="increasePresenterFontSize">
@@ -165,116 +169,120 @@ onMounted(() => {
165
169
  </IconButton>
166
170
  </div>
167
171
  </div>
168
- <div class="grid-section bottom">
172
+ <div class="grid-section bottom flex">
169
173
  <NavControls :persist="true" />
174
+ <div flex-auto />
175
+ <div
176
+ class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
177
+ opacity="50 hover:100"
178
+ @click="resetTimer"
179
+ >
180
+ <carbon:time class="absolute" />
181
+ <carbon:renew class="absolute opacity-0" />
182
+ </div>
183
+ <div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
184
+ {{ timer }}
185
+ </div>
170
186
  </div>
171
187
  <DrawingControls v-if="__SLIDEV_FEATURE_DRAWINGS__" />
172
188
  </div>
173
189
  <div class="progress-bar">
174
190
  <div
175
- class="progress h-2px bg-primary transition-all"
191
+ class="progress h-3px bg-primary transition-all"
176
192
  :style="{ width: `${(currentPage - 1) / (total - 1) * 100}%` }"
177
193
  />
178
194
  </div>
179
195
  </div>
180
196
  <Goto />
181
- <SlidesOverview v-model="showOverview" />
197
+ <QuickOverview v-model="showOverview" />
182
198
  </template>
183
199
 
184
- <style lang="postcss" scoped>
200
+ <style scoped>
185
201
  .slidev-presenter {
186
202
  --slidev-controls-foreground: current;
187
203
  }
188
204
 
189
- .timer-btn:hover {
190
- & > :first-child {
191
- @apply opacity-0;
192
- }
193
- & > :last-child {
194
- @apply opacity-100;
195
- }
205
+ .timer-btn:hover > :first-child {
206
+ opacity: 0;
207
+ }
208
+ .timer-btn:hover > :last-child {
209
+ opacity: 1;
196
210
  }
197
211
 
198
212
  .section-title {
199
- @apply px-4 py-2 text-xl;
213
+ --uno: px-4 py-2 text-xl;
200
214
  }
201
215
 
202
216
  .grid-container {
203
- @apply h-full w-full bg-gray-400 bg-opacity-15;
217
+ --uno: bg-gray/20;
218
+ height: 100%;
219
+ width: 100%;
204
220
  display: grid;
205
221
  gap: 1px 1px;
206
222
  }
207
223
 
208
224
  .grid-container.layout1 {
209
225
  grid-template-columns: 1fr 1fr;
210
- grid-template-rows: min-content 2fr 1fr min-content;
226
+ grid-template-rows: 2fr 1fr min-content;
211
227
  grid-template-areas:
212
- "top top"
213
- "main main"
214
- "note next"
215
- "bottom bottom";
228
+ 'main main'
229
+ 'note next'
230
+ 'bottom bottom';
216
231
  }
217
232
 
218
233
  .grid-container.layout2 {
219
234
  grid-template-columns: 3fr 2fr;
220
- grid-template-rows: min-content 2fr 1fr min-content;
235
+ grid-template-rows: 2fr 1fr min-content;
221
236
  grid-template-areas:
222
- "top top"
223
- "note main"
224
- "note next"
225
- "bottom bottom";
237
+ 'note main'
238
+ 'note next'
239
+ 'bottom bottom';
226
240
  }
227
241
 
228
242
  @media (max-aspect-ratio: 3/5) {
229
243
  .grid-container.layout1 {
230
244
  grid-template-columns: 1fr;
231
- grid-template-rows: min-content 1fr 1fr 1fr min-content;
245
+ grid-template-rows: 1fr 1fr 1fr min-content;
232
246
  grid-template-areas:
233
- "top"
234
- "main"
235
- "note"
236
- "next"
237
- "bottom";
247
+ 'main'
248
+ 'note'
249
+ 'next'
250
+ 'bottom';
238
251
  }
239
252
  }
240
253
 
241
254
  @media (min-aspect-ratio: 1/1) {
242
255
  .grid-container.layout1 {
243
256
  grid-template-columns: 1fr 1.1fr 0.9fr;
244
- grid-template-rows: min-content 1fr 2fr min-content;
257
+ grid-template-rows: 1fr 2fr min-content;
245
258
  grid-template-areas:
246
- "top top top"
247
- "main main next"
248
- "main main note"
249
- "bottom bottom bottom";
259
+ 'main main next'
260
+ 'main main note'
261
+ 'bottom bottom bottom';
250
262
  }
251
263
  }
252
264
 
253
265
  .progress-bar {
254
- @apply fixed left-0 right-0 bottom-0;
266
+ --uno: fixed left-0 right-0 top-0;
255
267
  }
256
268
 
257
269
  .grid-section {
258
- @apply bg-main;
259
-
260
- &.top {
261
- grid-area: top;
262
- }
263
- &.main {
264
- grid-area: main;
265
- }
266
- &.next {
267
- grid-area: next;
268
- }
269
- &.note {
270
- grid-area: note;
271
- }
272
- &.bottom {
273
- grid-area: bottom;
274
- }
270
+ --uno: bg-main;
275
271
  }
276
272
 
277
- .context {
278
- @apply absolute top-0 left-0 px-1 text-xs bg-gray-400 bg-opacity-50 opacity-75 rounded-br-md;
273
+ .grid-section.top {
274
+ grid-area: top;
275
+ }
276
+ .grid-section.main {
277
+ grid-area: main;
278
+ }
279
+ .grid-section.next {
280
+ grid-area: next;
281
+ }
282
+ .grid-section.note {
283
+ grid-area: note;
284
+ }
285
+ .grid-section.bottom {
286
+ grid-area: bottom;
279
287
  }
280
288
  </style>