@slidev/client 0.49.9 → 0.49.11

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.
@@ -2,8 +2,9 @@
2
2
  import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
3
3
  import type { KeyedTokensInfo } from 'shiki-magic-move/types'
4
4
  import type { PropType } from 'vue'
5
- import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
5
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
6
6
  import lz from 'lz-string'
7
+ import { sleep } from '@antfu/utils'
7
8
  import { useSlideContext } from '../context'
8
9
  import { makeId, updateCodeHighlightRange } from '../logic/utils'
9
10
  import { useNav } from '../composables/useNav'
@@ -72,9 +73,12 @@ onMounted(() => {
72
73
  }
73
74
  currentClickSum += current.length || 1
74
75
  }
75
- stepIndex.value = step
76
76
 
77
- setTimeout(() => {
77
+ nextTick(async () => {
78
+ stepIndex.value = step
79
+
80
+ await sleep(0)
81
+
78
82
  const pre = container.value?.querySelector('.shiki') as HTMLElement
79
83
  if (!pre)
80
84
  return
@@ -2,7 +2,7 @@ import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
2
2
  import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'
3
3
  import { createDrauu } from 'drauu'
4
4
  import { createSharedComposable, toReactive, useLocalStorage } from '@vueuse/core'
5
- import { drawingState, onPatch, patch } from '../state/drawings'
5
+ import { drawingState, onPatchDrawingState, patchDrawingState } from '../state/drawings'
6
6
  import { configs } from '../env'
7
7
  import { isInputting } from '../state'
8
8
  import { useNav } from './useNav'
@@ -67,7 +67,7 @@ export const useDrawings = createSharedComposable(() => {
67
67
  function clearDrauu() {
68
68
  drauu.clear()
69
69
  if (syncUp.value)
70
- patch(currentSlideNo.value, '')
70
+ patchDrawingState(currentSlideNo.value, '')
71
71
  }
72
72
 
73
73
  function updateState() {
@@ -93,11 +93,11 @@ export const useDrawings = createSharedComposable(() => {
93
93
  const dump = drauu.dump()
94
94
  const key = currentSlideNo.value
95
95
  if ((drawingState[key] || '') !== dump && syncUp.value)
96
- patch(key, drauu.dump())
96
+ patchDrawingState(key, drauu.dump())
97
97
  }
98
98
  })
99
99
 
100
- onPatch((state) => {
100
+ onPatchDrawingState((state) => {
101
101
  disableDump = true
102
102
  if (state[currentSlideNo.value] != null)
103
103
  drauu.load(state[currentSlideNo.value] || '')
@@ -0,0 +1,13 @@
1
+ import { useWakeLock as useVueUseWakeLock } from '@vueuse/core'
2
+ import { watchEffect } from 'vue'
3
+ import { wakeLockEnabled } from '../state'
4
+
5
+ export function useWakeLock() {
6
+ const { request, release } = useVueUseWakeLock()
7
+
8
+ watchEffect((onCleanup) => {
9
+ if (wakeLockEnabled.value)
10
+ request('screen')
11
+ onCleanup(release)
12
+ })
13
+ }
package/constants.ts CHANGED
@@ -80,4 +80,5 @@ export const HEADMATTER_FIELDS = [
80
80
  'htmlAttrs',
81
81
  'mdc',
82
82
  'contextMenu',
83
+ 'wakeLock',
83
84
  ]
@@ -4,9 +4,13 @@ import { clamp, range } from '@antfu/utils'
4
4
  import { computed } from 'vue'
5
5
  import { CLICKS_MAX } from '../constants'
6
6
 
7
- const props = defineProps<{
7
+ const props = withDefaults(defineProps<{
8
8
  clicksContext: ClicksContext
9
- }>()
9
+ readonly?: boolean
10
+ active?: boolean
11
+ }>(), {
12
+ active: true,
13
+ })
10
14
 
11
15
  const total = computed(() => props.clicksContext.total)
12
16
  const start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))
@@ -24,6 +28,8 @@ const current = computed({
24
28
  const clicksRange = computed(() => range(start.value, total.value + 1))
25
29
 
26
30
  function onMousedown() {
31
+ if (props.readonly)
32
+ return
27
33
  if (current.value < 0 || current.value > total.value)
28
34
  current.value = 0
29
35
  }
@@ -38,7 +44,7 @@ function onMousedown() {
38
44
  <div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
39
45
  <carbon:cursor-1 text-sm op50 />
40
46
  <div flex-auto />
41
- <template v-if="current >= 0 && current !== CLICKS_MAX">
47
+ <template v-if="current >= 0 && current !== CLICKS_MAX && active">
42
48
  <span text-primary>{{ current }}</span>
43
49
  <span op25>/</span>
44
50
  </template>
@@ -46,7 +52,6 @@ function onMousedown() {
46
52
  </div>
47
53
  <div
48
54
  relative flex-auto h5 font-mono flex="~"
49
- @dblclick="current = clicksContext.total"
50
55
  >
51
56
  <div
52
57
  v-for="i of clicksRange" :key="i"
@@ -71,8 +76,10 @@ function onMousedown() {
71
76
  </div>
72
77
  <input
73
78
  v-model="current"
74
- class="range" absolute inset-0
75
- type="range" :min="start" :max="total" :step="1" z-10 op0
79
+ class="range"
80
+ type="range" :min="start" :max="total" :step="1"
81
+ absolute inset-0 z-10 op0
82
+ :class="readonly ? 'pointer-events-none' : ''"
76
83
  :style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
77
84
  @mousedown="onMousedown"
78
85
  @focus="event => (event.currentTarget as HTMLElement)?.blur()"
@@ -3,14 +3,20 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
3
3
  import type { ClicksContext } from '@slidev/types'
4
4
  import { CLICKS_MAX } from '../constants'
5
5
 
6
- const props = defineProps<{
7
- class?: string | string[]
8
- noteHtml?: string
9
- note?: string
10
- placeholder?: string
11
- clicksContext?: ClicksContext
12
- autoScroll?: boolean
13
- }>()
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ class?: string | string[]
9
+ noteHtml?: string
10
+ note?: string
11
+ highlight?: boolean
12
+ placeholder?: string
13
+ clicksContext?: ClicksContext
14
+ autoScroll?: boolean
15
+ }>(),
16
+ {
17
+ highlight: true,
18
+ },
19
+ )
14
20
 
15
21
  const emit = defineEmits<{
16
22
  (type: 'markerDblclick', e: MouseEvent, clicks: number): void
@@ -30,7 +36,7 @@ function highlightNote() {
30
36
  const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
31
37
 
32
38
  const current = +(props.clicksContext?.current ?? CLICKS_MAX)
33
- const disabled = current < 0 || current >= CLICKS_MAX
39
+ const disabled = current < 0 || current >= CLICKS_MAX || !props.highlight
34
40
 
35
41
  const nodeToIgnores = new Set<Element>()
36
42
  function ignoreParent(node: Element) {
@@ -114,7 +120,7 @@ function highlightNote() {
114
120
  }
115
121
 
116
122
  watch(
117
- () => [props.noteHtml, props.clicksContext?.current],
123
+ () => [props.noteHtml, props.clicksContext?.current, props.highlight],
118
124
  () => {
119
125
  nextTick(() => {
120
126
  highlightNote()
@@ -26,6 +26,9 @@ const props = defineProps({
26
26
  clicksContext: {
27
27
  type: Object as PropType<ClicksContext>,
28
28
  },
29
+ highlight: {
30
+ default: true,
31
+ },
29
32
  autoHeight: {
30
33
  default: false,
31
34
  },
@@ -112,6 +115,7 @@ watch(
112
115
  :note-html="info?.noteHTML"
113
116
  :clicks-context="clicksContext"
114
117
  :auto-scroll="!autoHeight"
118
+ :highlight="props.highlight"
115
119
  @marker-click="(e, clicks) => emit('markerClick', e, clicks)"
116
120
  @marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
117
121
  />
@@ -5,7 +5,7 @@ import type { SelectionItem } from './types'
5
5
 
6
6
  const props = defineProps({
7
7
  modelValue: {
8
- type: [Object, String, Number] as PropType<any>,
8
+ type: [Object, String, Number, Boolean] as PropType<any>,
9
9
  },
10
10
  title: {
11
11
  type: String,
@@ -43,7 +43,7 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
43
43
 
44
44
  <style lang="postcss" scoped>
45
45
  .select-list {
46
- @apply py-2;
46
+ @apply my-2;
47
47
  }
48
48
 
49
49
  .item {
@@ -55,6 +55,6 @@ const value = useVModel(props, 'modelValue', emit, { passive: true })
55
55
  }
56
56
 
57
57
  .title {
58
- @apply text-xs uppercase opacity-50 tracking-widest px-7 py-1;
58
+ @apply text-xs uppercase opacity-50 tracking-widest px-7 py-1 select-none text-nowrap;
59
59
  }
60
60
  </style>
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { slideScale } from '../state'
2
+ import { useWakeLock } from '@vueuse/core'
3
+ import { slideScale, wakeLockEnabled } from '../state'
3
4
  import SelectList from './SelectList.vue'
4
5
  import type { SelectionItem } from './types'
5
6
 
6
- const items: SelectionItem<number>[] = [
7
+ const scaleItems: SelectionItem<number>[] = [
7
8
  {
8
9
  display: 'Fit',
9
10
  value: 0,
@@ -13,10 +14,24 @@ const items: SelectionItem<number>[] = [
13
14
  value: 1,
14
15
  },
15
16
  ]
17
+
18
+ const { isSupported } = useWakeLock()
19
+
20
+ const wakeLockItems: SelectionItem<boolean>[] = [
21
+ {
22
+ display: 'Enabled',
23
+ value: true,
24
+ },
25
+ {
26
+ display: 'Disabled',
27
+ value: false,
28
+ },
29
+ ]
16
30
  </script>
17
31
 
18
32
  <template>
19
33
  <div class="text-sm select-none">
20
- <SelectList v-model="slideScale" title="Scale" :items="items" />
34
+ <SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
35
+ <SelectList v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported" v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems" />
21
36
  </div>
22
37
  </template>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.49.9",
4
+ "version": "0.49.11",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -59,8 +59,8 @@
59
59
  "vue": "^3.4.27",
60
60
  "vue-router": "^4.3.2",
61
61
  "yaml": "^2.4.2",
62
- "@slidev/parser": "0.49.9",
63
- "@slidev/types": "0.49.9"
62
+ "@slidev/parser": "0.49.11",
63
+ "@slidev/types": "0.49.11"
64
64
  },
65
65
  "devDependencies": {
66
66
  "vite": "^5.2.12"
package/pages/notes.vue CHANGED
@@ -8,7 +8,9 @@ import { fullscreen } from '../state'
8
8
 
9
9
  import NoteDisplay from '../internals/NoteDisplay.vue'
10
10
  import IconButton from '../internals/IconButton.vue'
11
+ import ClicksSlider from '../internals/ClicksSlider.vue'
11
12
  import { useNav } from '../composables/useNav'
13
+ import { createClicksContextBase } from '../composables/useClicks'
12
14
 
13
15
  useHead({ title: `Notes - ${slidesTitle}` })
14
16
 
@@ -32,6 +34,12 @@ function increaseFontSize() {
32
34
  function decreaseFontSize() {
33
35
  fontSize.value = fontSize.value - 1
34
36
  }
37
+
38
+ const clicksContext = computed(() => {
39
+ const clicks = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicks : sharedState.clicks
40
+ const total = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicksTotal : sharedState.clicksTotal
41
+ return createClicksContextBase(ref(clicks), undefined, total)
42
+ })
35
43
  </script>
36
44
 
37
45
  <template>
@@ -49,10 +57,13 @@ function decreaseFontSize() {
49
57
  :note="currentRoute?.meta.slide.note"
50
58
  :note-html="currentRoute?.meta.slide.noteHTML"
51
59
  :placeholder="`No notes for Slide ${pageNo}.`"
52
- :clicks-context="currentRoute?.meta.__clicksContext"
60
+ :clicks-context="clicksContext"
53
61
  :auto-scroll="true"
54
62
  />
55
63
  </div>
64
+ <div class="flex-none border-t border-main" px3 py2>
65
+ <ClicksSlider :clicks-context="clicksContext" readonly />
66
+ </div>
56
67
  <div class="flex-none border-t border-main">
57
68
  <div class="flex gap-1 items-center px-6 py-3">
58
69
  <IconButton :title="isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'" @click="toggleFullscreen">
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, nextTick, onMounted, reactive, ref } from 'vue'
2
+ import { computed, nextTick, onMounted, reactive, ref, shallowRef } from 'vue'
3
3
  import { useHead } from '@unhead/vue'
4
4
  import type { ClicksContext, SlideRoute } from '@slidev/types'
5
5
  import { pathPrefix, slidesTitle } from '../env'
@@ -28,6 +28,7 @@ const wordCounts = computed(() => slides.value.map(route => wordCount(route.meta
28
28
  const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
29
29
  const totalClicks = computed(() => slides.value.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
30
30
 
31
+ const activeSlide = shallowRef<SlideRoute>()
31
32
  const clicksContextMap = new WeakMap<SlideRoute, ClicksContext>()
32
33
  function getClicksContext(route: SlideRoute) {
33
34
  // We create a local clicks context to calculate the total clicks of the slide
@@ -40,8 +41,15 @@ function getSlideClicks(route: SlideRoute) {
40
41
  return route.meta?.clicks || getClicksContext(route)?.total
41
42
  }
42
43
 
44
+ function toggleRoute(route: SlideRoute) {
45
+ if (activeSlide.value === route)
46
+ activeSlide.value = undefined
47
+ else
48
+ activeSlide.value = route
49
+ }
50
+
43
51
  function wordCount(str: string) {
44
- return str.match(/[\w'-]+/g)?.length || 0
52
+ return str.match(/[\w`'\-]+/g)?.length || 0
45
53
  }
46
54
 
47
55
  function isElementInViewport(el: HTMLElement) {
@@ -185,9 +193,11 @@ onMounted(() => {
185
193
  </div>
186
194
  <ClicksSlider
187
195
  v-if="getSlideClicks(route)"
196
+ :active="activeSlide === route"
188
197
  :clicks-context="getClicksContext(route)"
189
198
  class="w-full mt-2"
190
- @dblclick="getClicksContext(route).current = CLICKS_MAX"
199
+ @dblclick="toggleRoute(route)"
200
+ @click="activeSlide = route"
191
201
  />
192
202
  </div>
193
203
  <div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
@@ -204,6 +214,7 @@ onMounted(() => {
204
214
  :no="route.no"
205
215
  class="max-w-250 w-250 text-lg rounded p3"
206
216
  :auto-height="true"
217
+ :highlight="activeSlide === route"
207
218
  :editing="edittingNote === route.no"
208
219
  :clicks-context="getClicksContext(route)"
209
220
  @dblclick="edittingNote !== route.no ? edittingNote = route.no : null"
package/pages/play.vue CHANGED
@@ -10,6 +10,7 @@ import SlidesShow from '../internals/SlidesShow.vue'
10
10
  import PrintStyle from '../internals/PrintStyle.vue'
11
11
  import { onContextMenu } from '../logic/contextMenu'
12
12
  import { useNav } from '../composables/useNav'
13
+ import { useWakeLock } from '../composables/useWakeLock'
13
14
  import { useDrawings } from '../composables/useDrawings'
14
15
  import PresenterMouse from '../internals/PresenterMouse.vue'
15
16
 
@@ -32,6 +33,8 @@ function onClick(e: MouseEvent) {
32
33
 
33
34
  useSwipeControls(root)
34
35
  registerShortcuts()
36
+ if (__SLIDEV_FEATURE_WAKE_LOCK__)
37
+ useWakeLock()
35
38
 
36
39
  const persistNav = computed(() => isScreenVertical.value || showEditor.value)
37
40
 
@@ -24,11 +24,14 @@ import ClicksSlider from '../internals/ClicksSlider.vue'
24
24
  import ContextMenu from '../internals/ContextMenu.vue'
25
25
  import { useNav } from '../composables/useNav'
26
26
  import { useDrawings } from '../composables/useDrawings'
27
+ import { useWakeLock } from '../composables/useWakeLock'
27
28
 
28
29
  const main = ref<HTMLDivElement>()
29
30
 
30
31
  registerShortcuts()
31
32
  useSwipeControls(main)
33
+ if (__SLIDEV_FEATURE_WAKE_LOCK__)
34
+ useWakeLock()
32
35
 
33
36
  const {
34
37
  clicksContext,
package/setup/root.ts CHANGED
@@ -67,10 +67,12 @@ export default function setupRoot() {
67
67
  if (isPresenter.value) {
68
68
  patch('page', +currentSlideNo.value)
69
69
  patch('clicks', clicksContext.value.current)
70
+ patch('clicksTotal', clicksContext.value.total)
70
71
  }
71
72
  else {
72
73
  patch('viewerPage', +currentSlideNo.value)
73
74
  patch('viewerClicks', clicksContext.value.current)
75
+ patch('viewerClicksTotal', clicksContext.value.total)
74
76
  }
75
77
 
76
78
  patch('lastUpdate', {
package/state/drawings.ts CHANGED
@@ -3,5 +3,9 @@ import { createSyncState } from './syncState'
3
3
 
4
4
  export type DrawingsState = Record<number, string | undefined>
5
5
 
6
- const { init, onPatch, patch, state } = createSyncState<DrawingsState>(serverDrawingState, {}, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
7
- export { init as initDrawingState, onPatch, patch, state as drawingState }
6
+ export const {
7
+ init: initDrawingState,
8
+ onPatch: onPatchDrawingState,
9
+ patch: patchDrawingState,
10
+ state: drawingState,
11
+ } = createSyncState<DrawingsState>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
package/state/index.ts CHANGED
@@ -25,6 +25,7 @@ export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.v
25
25
  export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
26
26
  export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })
27
27
  export const slideScale = useLocalStorage<number>('slidev-scale', 0)
28
+ export const wakeLockEnabled = useLocalStorage('slidev-wake-lock', true)
28
29
 
29
30
  export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
30
31
  export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
package/state/shared.ts CHANGED
@@ -4,6 +4,8 @@ import { createSyncState } from './syncState'
4
4
  export interface SharedState {
5
5
  page: number
6
6
  clicks: number
7
+ clicksTotal: number
8
+
7
9
  cursor?: {
8
10
  x: number
9
11
  y: number
@@ -11,6 +13,7 @@ export interface SharedState {
11
13
 
12
14
  viewerPage: number
13
15
  viewerClicks: number
16
+ viewerClicksTotal: number
14
17
 
15
18
  lastUpdate?: {
16
19
  id: string
@@ -22,8 +25,10 @@ export interface SharedState {
22
25
  const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
23
26
  page: 1,
24
27
  clicks: 0,
28
+ clicksTotal: 0,
25
29
  viewerPage: 1,
26
30
  viewerClicks: 0,
31
+ viewerClicksTotal: 0,
27
32
  })
28
33
 
29
34
  export {