@slidev/client 0.48.0-beta.9 → 0.48.0

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 (97) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +33 -28
  4. package/builtin/KaTexBlockWrapper.vue +1 -1
  5. package/builtin/Link.vue +3 -1
  6. package/builtin/Mermaid.vue +4 -3
  7. package/builtin/Monaco.vue +166 -93
  8. package/builtin/ShikiMagicMove.vue +103 -0
  9. package/builtin/SlidevVideo.vue +1 -1
  10. package/builtin/Toc.vue +1 -1
  11. package/builtin/TocList.vue +4 -3
  12. package/builtin/Tweet.vue +12 -9
  13. package/builtin/VClick.ts +2 -1
  14. package/composables/useClicks.ts +19 -32
  15. package/composables/useDarkMode.ts +9 -0
  16. package/composables/useDrawings.ts +181 -0
  17. package/composables/useNav.ts +346 -44
  18. package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
  19. package/composables/useSwipeControls.ts +43 -0
  20. package/composables/useTocTree.ts +81 -0
  21. package/composables/useViewTransition.ts +7 -4
  22. package/constants.ts +4 -3
  23. package/context.ts +13 -6
  24. package/env.ts +7 -16
  25. package/index.html +1 -0
  26. package/index.ts +12 -0
  27. package/internals/ClicksSlider.vue +93 -0
  28. package/internals/CodeRunner.vue +142 -0
  29. package/internals/Controls.vue +2 -2
  30. package/internals/DomElement.vue +18 -0
  31. package/internals/DrawingControls.vue +14 -15
  32. package/internals/DrawingLayer.vue +6 -5
  33. package/internals/DrawingPreview.vue +4 -2
  34. package/internals/Goto.vue +9 -6
  35. package/internals/IconButton.vue +3 -2
  36. package/internals/NavControls.vue +30 -11
  37. package/internals/NoteDisplay.vue +131 -8
  38. package/internals/NoteEditable.vue +129 -0
  39. package/internals/NoteStatic.vue +5 -2
  40. package/internals/PrintContainer.vue +11 -8
  41. package/internals/PrintSlide.vue +11 -12
  42. package/internals/PrintSlideClick.vue +14 -19
  43. package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
  44. package/internals/RecordingControls.vue +1 -1
  45. package/internals/RecordingDialog.vue +3 -3
  46. package/internals/{Editor.vue → SideEditor.vue} +24 -15
  47. package/internals/SlideContainer.vue +13 -9
  48. package/internals/SlideLoading.vue +19 -0
  49. package/internals/SlideWrapper.vue +79 -0
  50. package/internals/SlidesShow.vue +36 -22
  51. package/layouts/error.vue +5 -0
  52. package/layouts/two-cols-header.vue +9 -3
  53. package/logic/overview.ts +2 -2
  54. package/logic/route.ts +16 -5
  55. package/logic/slides.ts +20 -0
  56. package/logic/transition.ts +50 -0
  57. package/logic/utils.ts +24 -1
  58. package/main.ts +3 -15
  59. package/{setup → modules}/codemirror.ts +1 -3
  60. package/modules/context.ts +1 -46
  61. package/modules/mermaid.ts +9 -8
  62. package/package.json +21 -15
  63. package/pages/notes.vue +6 -3
  64. package/pages/overview.vue +138 -51
  65. package/pages/play.vue +16 -9
  66. package/pages/presenter/print.vue +10 -5
  67. package/pages/presenter.vue +122 -104
  68. package/pages/print.vue +4 -3
  69. package/routes.ts +8 -54
  70. package/setup/code-runners.ts +164 -0
  71. package/setup/main.ts +39 -9
  72. package/setup/mermaid.ts +5 -6
  73. package/setup/monaco.ts +114 -51
  74. package/setup/root.ts +62 -18
  75. package/setup/shortcuts.ts +15 -12
  76. package/shim-vue.d.ts +34 -0
  77. package/shim.d.ts +1 -13
  78. package/state/index.ts +2 -2
  79. package/styles/code.css +9 -5
  80. package/styles/index.css +63 -7
  81. package/styles/katex.css +1 -1
  82. package/styles/layouts-base.css +11 -8
  83. package/styles/shiki-twoslash.css +1 -1
  84. package/styles/vars.css +1 -1
  85. package/uno.config.ts +10 -1
  86. package/utils.ts +15 -2
  87. package/composables/useContext.ts +0 -17
  88. package/composables/useTweetScript.ts +0 -17
  89. package/iframes/monaco/index.css +0 -28
  90. package/iframes/monaco/index.html +0 -7
  91. package/iframes/monaco/index.ts +0 -260
  92. package/internals/NoteEditor.vue +0 -92
  93. package/internals/SlideWrapper.ts +0 -58
  94. package/logic/drawings.ts +0 -161
  95. package/logic/nav.ts +0 -278
  96. package/setup/prettier.ts +0 -43
  97. /package/{composables → logic}/hmr.ts +0 -0
@@ -1,21 +1,22 @@
1
1
  <script setup lang="ts">
2
2
  import { Menu } from 'floating-vue'
3
- import 'floating-vue/dist/style.css'
4
- import {
3
+ import { useDrawings } from '../composables/useDrawings'
4
+ import VerticalDivider from './VerticalDivider.vue'
5
+ import Draggable from './Draggable.vue'
6
+ import IconButton from './IconButton.vue'
7
+
8
+ const {
5
9
  brush,
6
- brushColors,
7
10
  canClear,
8
11
  canRedo,
9
12
  canUndo,
10
- clearDrauu,
13
+ clear,
11
14
  drauu,
12
15
  drawingEnabled,
13
16
  drawingMode,
14
17
  drawingPinned,
15
- } from '../logic/drawings'
16
- import VerticalDivider from './VerticalDivider.vue'
17
- import Draggable from './Draggable.vue'
18
- import IconButton from './IconButton.vue'
18
+ brushColors,
19
+ } = useDrawings()
19
20
 
20
21
  function undo() {
21
22
  drauu.undo()
@@ -110,7 +111,7 @@ function setBrushColor(color: typeof brush.color) {
110
111
  <IconButton title="Redo" :class="{ disabled: !canRedo }" @click="redo()">
111
112
  <carbon:redo />
112
113
  </IconButton>
113
- <IconButton title="Delete" :class="{ disabled: !canClear }" @click="clearDrauu()">
114
+ <IconButton title="Delete" :class="{ disabled: !canClear }" @click="clear()">
114
115
  <carbon:trash-can />
115
116
  </IconButton>
116
117
 
@@ -131,10 +132,8 @@ function setBrushColor(color: typeof brush.color) {
131
132
  </Draggable>
132
133
  </template>
133
134
 
134
- <style lang="postcss">
135
- .v-popper--theme-menu {
136
- .v-popper__arrow-inner {
137
- @apply border-main;
138
- }
135
+ <style>
136
+ .v-popper--theme-menu .v-popper__arrow-inner {
137
+ --uno: border-main;
139
138
  }
140
- </style>
139
+ </style>../composables/drawings
@@ -1,10 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
- import { injectLocal } from '@vueuse/core'
4
- import { drauu, drawingEnabled, loadCanvas } from '../logic/drawings'
5
- import { injectionSlideScale } from '../constants'
3
+ import { useSlideContext } from '../context'
4
+ import { useDrawings } from '../composables/useDrawings'
6
5
 
7
- const scale = injectLocal(injectionSlideScale)!
6
+ const { drauu, drawingEnabled, loadCanvas } = useDrawings()
7
+
8
+ const scale = useSlideContext().$scale
8
9
  const svg = ref<SVGSVGElement>()
9
10
 
10
11
  onMounted(() => {
@@ -24,4 +25,4 @@ onBeforeUnmount(() => {
24
25
  class="w-full h-full absolute top-0"
25
26
  :class="{ 'pointer-events-none': !drawingEnabled, 'touch-none': drawingEnabled }"
26
27
  />
27
- </template>
28
+ </template>../composables/drawings
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { drawingState } from '../logic/drawings'
2
+ import { useDrawings } from '../composables/useDrawings'
3
3
 
4
4
  defineProps<{ page: number }>()
5
+
6
+ const { drawingState } = useDrawings()
5
7
  </script>
6
8
 
7
9
  <template>
@@ -10,4 +12,4 @@ defineProps<{ page: number }>()
10
12
  class="w-full h-full absolute top-0 pointer-events-none"
11
13
  v-html="drawingState[page]"
12
14
  />
13
- </template>
15
+ </template>../composables/drawings
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import Fuse from 'fuse.js'
4
- import { go, rawRoutes } from '../logic/nav'
5
4
  import { activeElement, showGotoDialog } from '../state'
6
- import Titles from '/@slidev/titles.md'
5
+ import { useNav } from '../composables/useNav'
6
+ import Titles from '#slidev/title-renderer'
7
7
 
8
8
  const container = ref<HTMLDivElement>()
9
9
  const input = ref<HTMLInputElement>()
@@ -12,11 +12,13 @@ const items = ref<HTMLLIElement[]>()
12
12
  const text = ref('')
13
13
  const selectedIndex = ref(0)
14
14
 
15
+ const { go, slides } = useNav()
16
+
15
17
  function notNull<T>(value: T | null | undefined): value is T {
16
18
  return value !== null && value !== undefined
17
19
  }
18
20
 
19
- const fuse = computed(() => new Fuse(rawRoutes.map(i => i.meta?.slide).filter(notNull), {
21
+ const fuse = computed(() => new Fuse(slides.value.map(i => i.meta?.slide).filter(notNull), {
20
22
  keys: ['no', 'title'],
21
23
  threshold: 0.3,
22
24
  shouldSort: true,
@@ -165,10 +167,11 @@ watch(activeElement, () => {
165
167
  </div>
166
168
  </template>
167
169
 
168
- <style scoped lang="postcss">
170
+ <style scoped>
169
171
  .autocomplete-list {
170
- @apply bg-main transform mt-1 overflow-auto;
171
- max-height: calc( 100vh - 100px );
172
+ --uno: bg-main mt-1;
173
+ overflow: auto;
174
+ max-height: calc(100vh - 100px);
172
175
  }
173
176
 
174
177
  .autocomplete {
@@ -2,14 +2,15 @@
2
2
  defineProps<{
3
3
  title: string
4
4
  icon?: string
5
+ as?: string
5
6
  }>()
6
7
  </script>
7
8
 
8
9
  <template>
9
- <button class="slidev-icon-btn" :title="title" v-bind="$attrs">
10
+ <component :is="as || 'button'" class="slidev-icon-btn" :title="title">
10
11
  <span class="sr-only">{{ title }}</span>
11
12
  <slot>
12
13
  <div :class="icon" />
13
14
  </slot>
14
- </button>
15
+ </component>
15
16
  </template>
@@ -1,17 +1,18 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, shallowRef } from 'vue'
3
3
  import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
4
- import { currentPage, downloadPDF, hasNext, hasPrev, isEmbedded, isPresenter, next, presenterPassword, prev, showPresenter, total } from '../logic/nav'
4
+ import { downloadPDF } from '../utils'
5
5
  import { activeElement, breakpoints, fullscreen, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterLayout } from '../state'
6
- import { brush, drawingEnabled } from '../logic/drawings'
7
6
  import { configs } from '../env'
7
+ import { useNav } from '../composables/useNav'
8
+ import { getSlidePath } from '../logic/slides'
9
+ import { useDrawings } from '../composables/useDrawings'
8
10
  import Settings from './Settings.vue'
9
11
  import MenuButton from './MenuButton.vue'
10
12
  import VerticalDivider from './VerticalDivider.vue'
11
13
  import IconButton from './IconButton.vue'
12
14
 
13
- // @ts-expect-error virtual module
14
- import CustomNavControls from '/@slidev/custom-nav-controls'
15
+ import CustomNavControls from '#slidev/custom-nav-controls'
15
16
 
16
17
  const props = defineProps({
17
18
  persist: {
@@ -19,12 +20,30 @@ const props = defineProps({
19
20
  },
20
21
  })
21
22
 
23
+ const {
24
+ currentRoute,
25
+ currentSlideNo,
26
+ hasNext,
27
+ hasPrev,
28
+ isEmbedded,
29
+ isPresenter,
30
+ isPresenterAvailable,
31
+ next,
32
+ prev,
33
+ total,
34
+ } = useNav()
35
+ const {
36
+ brush,
37
+ drawingEnabled,
38
+ } = useDrawings()
39
+
22
40
  const md = breakpoints.smaller('md')
23
41
  const { isFullscreen, toggle: toggleFullscreen } = fullscreen
24
42
 
43
+ const presenterPassword = computed(() => currentRoute.value.query.password)
25
44
  const query = computed(() => presenterPassword.value ? `?password=${presenterPassword.value}` : '')
26
- const presenterLink = computed(() => `/presenter/${currentPage.value}${query.value}`)
27
- const nonPresenterLink = computed(() => `/${currentPage.value}${query.value}`)
45
+ const presenterLink = computed(() => `${getSlidePath(currentSlideNo.value, true)}${query.value}`)
46
+ const nonPresenterLink = computed(() => `${getSlidePath(currentSlideNo.value, false)}${query.value}`)
28
47
 
29
48
  const root = ref<HTMLDivElement>()
30
49
  function onMouseLeave() {
@@ -108,20 +127,20 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
108
127
  <RouterLink v-if="isPresenter" :to="nonPresenterLink" class="slidev-icon-btn" title="Play Mode">
109
128
  <carbon:presentation-file />
110
129
  </RouterLink>
111
- <RouterLink v-if="__SLIDEV_FEATURE_PRESENTER__ && showPresenter" :to="presenterLink" class="slidev-icon-btn" title="Presenter Mode">
130
+ <RouterLink v-if="__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable" :to="presenterLink" class="slidev-icon-btn" title="Presenter Mode">
112
131
  <carbon:user-speaker />
113
132
  </RouterLink>
114
133
 
115
134
  <IconButton
116
135
  v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__"
117
136
  :title="showEditor ? 'Hide editor' : 'Show editor'"
118
- class="<md:hidden"
137
+ class="lt-md:hidden"
119
138
  @click="showEditor = !showEditor"
120
139
  >
121
140
  <carbon:text-annotation-toggle />
122
141
  </IconButton>
123
142
 
124
- <IconButton v-if="isPresenter" title="Toggle Presenter Layout" @click="togglePresenterLayout">
143
+ <IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial" @click="togglePresenterLayout">
125
144
  <carbon:template />
126
145
  {{ presenterLayout }}
127
146
  </IconButton>
@@ -157,7 +176,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
157
176
 
158
177
  <div class="h-40px flex" p="l-1 t-0.5 r-2" text="sm leading-2">
159
178
  <div class="my-auto">
160
- {{ currentPage }}
179
+ {{ currentSlideNo }}
161
180
  <span class="opacity-50">/ {{ total }}</span>
162
181
  </div>
163
182
  </div>
@@ -165,4 +184,4 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
165
184
  <CustomNavControls />
166
185
  </div>
167
186
  </nav>
168
- </template>
187
+ </template>../composables/drawings
@@ -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 select-none"
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,129 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from 'vue'
3
+ import { nextTick, ref, toRef, watch, watchEffect } from 'vue'
4
+ import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
5
+ import type { ClicksContext } from '@slidev/types'
6
+ import { useDynamicSlideInfo } from '../composables/useSlideInfo'
7
+ import NoteDisplay from './NoteDisplay.vue'
8
+
9
+ const props = defineProps({
10
+ no: {
11
+ type: Number,
12
+ required: true,
13
+ },
14
+ class: {
15
+ default: '',
16
+ },
17
+ editing: {
18
+ default: false,
19
+ },
20
+ style: {
21
+ default: () => ({}),
22
+ },
23
+ placeholder: {
24
+ default: 'No notes for this slide',
25
+ },
26
+ clicksContext: {
27
+ type: Object as PropType<ClicksContext>,
28
+ },
29
+ autoHeight: {
30
+ default: false,
31
+ },
32
+ })
33
+
34
+ const emit = defineEmits<{
35
+ (type: 'update:editing', value: boolean): void
36
+ (type: 'markerDblclick', e: MouseEvent, clicks: number): void
37
+ (type: 'markerClick', e: MouseEvent, clicks: number): void
38
+ }>()
39
+
40
+ const editing = useVModel(props, 'editing', emit, { passive: true })
41
+
42
+ const { info, update } = useDynamicSlideInfo(toRef(props, 'no'))
43
+
44
+ const note = ref('')
45
+ let timer: any
46
+
47
+ // Send back the note on changes
48
+ const { ignoreUpdates } = ignorableWatch(
49
+ note,
50
+ (v) => {
51
+ if (!editing.value)
52
+ return
53
+ const id = props.no
54
+ clearTimeout(timer)
55
+ timer = setTimeout(() => {
56
+ update({ note: v }, id)
57
+ }, 500)
58
+ },
59
+ )
60
+
61
+ // Update note value when info changes
62
+ watch(
63
+ () => info.value?.note,
64
+ (value = '') => {
65
+ if (editing.value)
66
+ return
67
+ clearTimeout(timer)
68
+ ignoreUpdates(() => {
69
+ note.value = value
70
+ })
71
+ },
72
+ { immediate: true, flush: 'sync' },
73
+ )
74
+
75
+ const inputEl = ref<HTMLTextAreaElement>()
76
+ const inputHeight = ref<number | null>()
77
+
78
+ watchEffect(() => {
79
+ if (editing.value)
80
+ inputEl.value?.focus()
81
+ })
82
+
83
+ onClickOutside(inputEl, () => {
84
+ editing.value = false
85
+ })
86
+
87
+ function calculateEditorHeight() {
88
+ if (!props.autoHeight || !inputEl.value || !editing.value)
89
+ return
90
+ if (inputEl.value.scrollHeight > inputEl.value.clientHeight)
91
+ inputEl.value.style.height = `${inputEl.value.scrollHeight}px`
92
+ }
93
+
94
+ watch(
95
+ [note, editing],
96
+ () => {
97
+ nextTick(() => {
98
+ calculateEditorHeight()
99
+ })
100
+ },
101
+ { flush: 'post', immediate: true },
102
+ )
103
+ </script>
104
+
105
+ <template>
106
+ <NoteDisplay
107
+ v-if="!editing"
108
+ class="border-transparent border-2"
109
+ :class="[props.class, note ? '' : 'opacity-25 italic select-none']"
110
+ :style="props.style"
111
+ :note="note || placeholder"
112
+ :note-html="info?.noteHTML"
113
+ :clicks-context="clicksContext"
114
+ :auto-scroll="!autoHeight"
115
+ @marker-click="(e, clicks) => emit('markerClick', e, clicks)"
116
+ @marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
117
+ />
118
+ <textarea
119
+ v-else
120
+ ref="inputEl"
121
+ v-model="note"
122
+ class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
123
+ style="line-height: 1.75;"
124
+ :style="[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]"
125
+ :class="props.class"
126
+ :placeholder="placeholder"
127
+ @keydown.esc="editing = false"
128
+ />
129
+ </template>
@@ -1,10 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { useSlideInfo } from '../logic/note'
2
+ import type { ClicksContext } from '@slidev/types'
3
+ import { useSlideInfo } from '../composables/useSlideInfo'
3
4
  import NoteDisplay from './NoteDisplay.vue'
4
5
 
5
6
  const props = defineProps<{
6
- no?: number
7
+ no: number
7
8
  class?: string
9
+ clicksContext?: ClicksContext
8
10
  }>()
9
11
 
10
12
  const { info } = useSlideInfo(props.no)
@@ -15,5 +17,6 @@ const { info } = useSlideInfo(props.no)
15
17
  :class="props.class"
16
18
  :note="info?.note"
17
19
  :note-html="info?.noteHTML"
20
+ :clicks-context="clicksContext"
18
21
  />
19
22
  </template>
@@ -4,25 +4,28 @@ import { computed } from 'vue'
4
4
  import { provideLocal } from '@vueuse/core'
5
5
  import { configs, slideAspect, slideWidth } from '../env'
6
6
  import { injectionSlideScale } from '../constants'
7
- import { route as currentRoute, rawRoutes } from '../logic/nav'
7
+ import { useNav } from '../composables/useNav'
8
8
  import PrintSlide from './PrintSlide.vue'
9
9
 
10
10
  const props = defineProps<{
11
11
  width: number
12
12
  }>()
13
13
 
14
+ const { slides, currentRoute } = useNav()
15
+
14
16
  const width = computed(() => props.width)
15
- const height = computed(() => props.width / slideAspect)
17
+ const height = computed(() => props.width / slideAspect.value)
16
18
 
17
19
  const screenAspect = computed(() => width.value / height.value)
18
20
 
19
21
  const scale = computed(() => {
20
- if (screenAspect.value < slideAspect)
21
- return width.value / slideWidth
22
- return (height.value * slideAspect) / slideWidth
22
+ if (screenAspect.value < slideAspect.value)
23
+ return width.value / slideWidth.value
24
+ return (height.value * slideAspect.value) / slideWidth.value
23
25
  })
24
26
 
25
- let routes = rawRoutes
27
+ // In print mode, the routes will never change. So we don't need reactivity here.
28
+ let routes = slides.value
26
29
  if (currentRoute.value.query.range) {
27
30
  const r = parseRangeString(routes.length, currentRoute.value.query.range as string)
28
31
  routes = r.map(i => routes[i - 1])
@@ -38,7 +41,7 @@ provideLocal(injectionSlideScale, scale)
38
41
  <template>
39
42
  <div id="print-container" :class="className">
40
43
  <div id="print-content">
41
- <PrintSlide v-for="route of routes" :key="route.path" :route="route" />
44
+ <PrintSlide v-for="route of routes" :key="route.no" :route="route" />
42
45
  </div>
43
46
  <slot name="controls" />
44
47
  </div>
@@ -50,6 +53,6 @@ provideLocal(injectionSlideScale, scale)
50
53
  }
51
54
 
52
55
  .print-slide-container {
53
- @apply relative overflow-hidden break-after-page;
56
+ @apply relative overflow-hidden break-after-page translate-0;
54
57
  }
55
58
  </style>
@@ -1,24 +1,23 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
- import { computed } from 'vue'
4
- import { useNav } from '../composables/useNav'
5
- import { useFixedClicks } from '../composables/useClicks'
2
+ import type { SlideRoute } from '@slidev/types'
3
+ import { useFixedNav } from '../composables/useNav'
4
+ import { createFixedClicks } from '../composables/useClicks'
6
5
  import PrintSlideClick from './PrintSlideClick.vue'
7
6
 
8
- const props = defineProps<{ route: RouteRecordRaw }>()
9
-
10
- const route = computed(() => props.route)
11
- const nav = useNav(route)
12
- const clicks0 = useFixedClicks(route.value, 0)[1]
7
+ const { route } = defineProps<{ route: SlideRoute }>()
8
+ const clicks0 = createFixedClicks(route, 0)
13
9
  </script>
14
10
 
15
11
  <template>
16
12
  <PrintSlideClick
17
13
  :clicks-context="clicks0"
18
- :nav="nav"
19
- :route="route"
14
+ :nav="useFixedNav(route, clicks0)"
20
15
  />
21
16
  <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" />
17
+ <PrintSlideClick
18
+ v-for="i of clicks0.total"
19
+ :key="i"
20
+ :nav="useFixedNav(route, createFixedClicks(route, i))"
21
+ />
23
22
  </template>
24
23
  </template>