@slidev/client 0.48.0-beta.14 → 0.48.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/builtin/Arrow.vue CHANGED
@@ -9,7 +9,7 @@ Simple Arrow
9
9
  -->
10
10
 
11
11
  <script setup lang="ts">
12
- import { customAlphabet } from 'nanoid'
12
+ import { makeId } from '../logic/utils'
13
13
 
14
14
  defineProps<{
15
15
  x1: number | string
@@ -20,9 +20,7 @@ defineProps<{
20
20
  color?: string
21
21
  }>()
22
22
 
23
- const nanoid = customAlphabet('abcedfghijklmn', 10)
24
-
25
- const id = nanoid()
23
+ const id = makeId()
26
24
  </script>
27
25
 
28
26
  <template>
@@ -19,7 +19,7 @@ import ShadowRoot from '../internals/ShadowRoot.vue'
19
19
  import { isDark } from '../logic/dark'
20
20
 
21
21
  const props = defineProps<{
22
- code: string
22
+ codeLz: string
23
23
  scale?: number
24
24
  theme?: string
25
25
  }>()
@@ -37,7 +37,7 @@ watchEffect(async (onCleanup) => {
37
37
  error.value = null
38
38
  try {
39
39
  const svg = await renderMermaid(
40
- props.code || '',
40
+ props.codeLz || '',
41
41
  {
42
42
  theme: props.theme || (isDark.value ? 'dark' : undefined),
43
43
  ...vm!.attrs,
@@ -14,30 +14,30 @@ Learn more: https://sli.dev/guide/syntax.html#monaco-editor
14
14
  <script setup lang="ts">
15
15
  import { computed, onMounted, ref } from 'vue'
16
16
  import { useEventListener } from '@vueuse/core'
17
- import { decode } from 'js-base64'
18
- import { nanoid } from 'nanoid'
19
17
  import type * as monaco from 'monaco-editor'
18
+ import { decompressFromBase64 } from 'lz-string'
20
19
  import { isDark } from '../logic/dark'
20
+ import { makeId } from '../logic/utils'
21
21
 
22
22
  const props = withDefaults(defineProps<{
23
- code: string
24
- diff?: string
23
+ codeLz: string
24
+ diffLz?: string
25
25
  lang?: string
26
26
  readonly?: boolean
27
27
  lineNumbers?: 'on' | 'off' | 'relative' | 'interval'
28
28
  height?: number | string
29
29
  editorOptions?: monaco.editor.IEditorOptions
30
30
  }>(), {
31
- code: '',
31
+ codeLz: '',
32
32
  lang: 'typescript',
33
33
  readonly: false,
34
34
  lineNumbers: 'off',
35
35
  height: 'auto',
36
36
  })
37
37
 
38
- const id = nanoid()
39
- const code = ref(decode(props.code).trimEnd())
40
- const diff = ref(props.diff ? decode(props.diff).trimEnd() : null)
38
+ const id = makeId()
39
+ const code = ref(decompressFromBase64(props.codeLz))
40
+ const diff = ref(props.diffLz ? decompressFromBase64(props.diffLz) : null)
41
41
  const lineHeight = +(getComputedStyle(document.body).getPropertyValue('--slidev-code-line-height') || '18').replace('px', '') || 18
42
42
  const editorHeight = ref(0)
43
43
  const calculatedHeight = computed(() => code.value.split(/\r?\n/g).length * lineHeight)
@@ -2,16 +2,18 @@
2
2
  import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
3
3
  import type { KeyedTokensInfo } from 'shiki-magic-move/types'
4
4
  import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
5
+ import { decompressFromBase64 } from 'lz-string'
5
6
  import { useSlideContext } from '../context'
6
7
  import { makeId } from '../logic/utils'
7
8
 
8
9
  import 'shiki-magic-move/style.css'
9
10
 
10
11
  const props = defineProps<{
11
- steps: KeyedTokensInfo[]
12
+ stepsLz: string
12
13
  at?: string | number
13
14
  }>()
14
15
 
16
+ const steps = JSON.parse(decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
15
17
  const { $clicksContext: clicks, $scale: scale } = useSlideContext()
16
18
  const id = makeId()
17
19
  const index = ref(0)
@@ -24,14 +26,14 @@ onMounted(() => {
24
26
  if (!clicks || clicks.disabled)
25
27
  return
26
28
 
27
- const { start, end, delta } = clicks.resolve(props.at || '+1', props.steps.length - 1)
29
+ const { start, end, delta } = clicks.resolve(props.at || '+1', steps.length - 1)
28
30
  clicks.register(id, { max: end, delta })
29
31
 
30
32
  watchEffect(() => {
31
33
  if (clicks.disabled)
32
- index.value = props.steps.length - 1
34
+ index.value = steps.length - 1
33
35
  else
34
- index.value = Math.min(Math.max(0, clicks.current - start + 1), props.steps.length - 1)
36
+ index.value = Math.min(Math.max(0, clicks.current - start + 1), steps.length - 1)
35
37
  })
36
38
  })
37
39
  </script>
@@ -12,7 +12,10 @@ const props = defineProps<{
12
12
  autoScroll?: boolean
13
13
  }>()
14
14
 
15
- defineEmits(['click'])
15
+ const emit = defineEmits<{
16
+ (type: 'markerDblclick', e: MouseEvent, clicks: number): void
17
+ (type: 'markerClick', e: MouseEvent, clicks: number): void
18
+ }>()
16
19
 
17
20
  const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark'))
18
21
  const noteDisplay = ref<HTMLElement | null>(null)
@@ -21,16 +24,13 @@ const CLASS_FADE = 'slidev-note-fade'
21
24
  const CLASS_MARKER = 'slidev-note-click-mark'
22
25
 
23
26
  function highlightNote() {
24
- if (!noteDisplay.value || !withClicks.value || props.clicksContext?.current == null)
27
+ if (!noteDisplay.value || !withClicks.value)
25
28
  return
26
29
 
27
- const current = +props.clicksContext?.current ?? CLICKS_MAX
30
+ const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
31
+
32
+ const current = +(props.clicksContext?.current ?? CLICKS_MAX)
28
33
  const disabled = current < 0 || current >= CLICKS_MAX
29
- if (disabled) {
30
- Array.from(noteDisplay.value.querySelectorAll('*'))
31
- .forEach(el => el.classList.remove(CLASS_FADE))
32
- return
33
- }
34
34
 
35
35
  const nodeToIgnores = new Set<Element>()
36
36
  function ignoreParent(node: Element) {
@@ -41,7 +41,6 @@ function highlightNote() {
41
41
  ignoreParent(node.parentElement)
42
42
  }
43
43
 
44
- const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
45
44
  const markersMap = new Map<number, HTMLElement>()
46
45
 
47
46
  // Convert all sibling text nodes to spans, so we attach classes to them
@@ -78,25 +77,36 @@ function highlightNote() {
78
77
 
79
78
  // Apply
80
79
  for (const [count, els] of segments) {
81
- els.forEach(el => el.classList.toggle(
82
- CLASS_FADE,
83
- nodeToIgnores.has(el)
84
- ? false
85
- : count !== current,
86
- ))
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
+ }
87
91
  }
88
92
 
89
93
  for (const [clicks, marker] of markersMap) {
90
94
  marker.classList.remove(CLASS_FADE)
91
- marker.classList.toggle(`${CLASS_MARKER}-past`, clicks < current)
92
- marker.classList.toggle(`${CLASS_MARKER}-active`, clicks === current)
93
- marker.classList.toggle(`${CLASS_MARKER}-next`, clicks === current + 1)
94
- marker.classList.toggle(`${CLASS_MARKER}-future`, clicks > current + 1)
95
- marker.addEventListener('dblclick', (e) => {
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
96
103
  props.clicksContext!.current = clicks
97
104
  e.stopPropagation()
98
105
  e.stopImmediatePropagation()
99
- })
106
+ }
107
+ marker.onclick = (e) => {
108
+ emit('markerClick', e, clicks)
109
+ }
100
110
 
101
111
  if (props.autoScroll && clicks === current)
102
112
  marker.scrollIntoView({ block: 'center', behavior: 'smooth' })
@@ -124,14 +134,12 @@ onMounted(() => {
124
134
  ref="noteDisplay"
125
135
  class="prose overflow-auto outline-none slidev-note"
126
136
  :class="[props.class, withClicks ? 'slidev-note-with-clicks' : '']"
127
- @click="$emit('click')"
128
137
  v-html="noteHtml"
129
138
  />
130
139
  <div
131
140
  v-else-if="note"
132
141
  class="prose overflow-auto outline-none slidev-note"
133
142
  :class="props.class"
134
- @click="$emit('click')"
135
143
  >
136
144
  <p v-text="note" />
137
145
  </div>
@@ -139,7 +147,6 @@ onMounted(() => {
139
147
  v-else
140
148
  class="prose overflow-auto outline-none opacity-50 italic select-none slidev-note"
141
149
  :class="props.class"
142
- @click="$emit('click')"
143
150
  >
144
151
  <p v-text="props.placeholder || 'No notes.'" />
145
152
  </div>
@@ -30,9 +30,12 @@ const props = defineProps({
30
30
  },
31
31
  })
32
32
 
33
- const emit = defineEmits([
34
- 'update:editing',
35
- ])
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
+
36
39
  const editing = useVModel(props, 'editing', emit, { passive: true })
37
40
 
38
41
  const { info, update } = useDynamicSlideInfo(props.no)
@@ -40,9 +43,12 @@ const { info, update } = useDynamicSlideInfo(props.no)
40
43
  const note = ref('')
41
44
  let timer: any
42
45
 
46
+ // Send back the note on changes
43
47
  const { ignoreUpdates } = ignorableWatch(
44
48
  note,
45
49
  (v) => {
50
+ if (!editing.value)
51
+ return
46
52
  const id = props.no
47
53
  clearTimeout(timer)
48
54
  timer = setTimeout(() => {
@@ -51,46 +57,44 @@ const { ignoreUpdates } = ignorableWatch(
51
57
  },
52
58
  )
53
59
 
60
+ // Update note value when info changes
54
61
  watch(
55
- info,
56
- (v) => {
62
+ () => info.value?.note,
63
+ (value = '') => {
57
64
  if (editing.value)
58
65
  return
59
66
  clearTimeout(timer)
60
67
  ignoreUpdates(() => {
61
- note.value = v?.note || ''
68
+ note.value = value
62
69
  })
63
70
  },
64
71
  { immediate: true, flush: 'sync' },
65
72
  )
66
73
 
67
- const input = ref<HTMLTextAreaElement>()
74
+ const inputEl = ref<HTMLTextAreaElement>()
75
+ const inputHeight = ref<number | null>()
68
76
 
69
77
  watchEffect(() => {
70
78
  if (editing.value)
71
- input.value?.focus()
79
+ inputEl.value?.focus()
72
80
  })
73
81
 
74
- onClickOutside(input, () => {
82
+ onClickOutside(inputEl, () => {
75
83
  editing.value = false
76
84
  })
77
85
 
78
- function calculateHeight() {
79
- if (!props.autoHeight || !input.value || !editing.value)
86
+ function calculateEditorHeight() {
87
+ if (!props.autoHeight || !inputEl.value || !editing.value)
80
88
  return
81
- if (input.value.scrollHeight > input.value.clientHeight)
82
- input.value.style.height = `${input.value.scrollHeight}px`
89
+ if (inputEl.value.scrollHeight > inputEl.value.clientHeight)
90
+ inputEl.value.style.height = `${inputEl.value.scrollHeight}px`
83
91
  }
84
92
 
85
- const inputHeight = ref<number | null>()
86
-
87
93
  watch(
88
94
  note,
89
- () => {
90
- nextTick(() => {
91
- calculateHeight()
92
- })
93
- },
95
+ () => nextTick(() => {
96
+ calculateEditorHeight()
97
+ }),
94
98
  { flush: 'post', immediate: true },
95
99
  )
96
100
  </script>
@@ -105,10 +109,12 @@ watch(
105
109
  :note-html="info?.noteHTML"
106
110
  :clicks-context="clicksContext"
107
111
  :auto-scroll="!autoHeight"
112
+ @marker-click="(e, clicks) => emit('markerClick', e, clicks)"
113
+ @marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
108
114
  />
109
115
  <textarea
110
116
  v-else
111
- ref="input"
117
+ ref="inputEl"
112
118
  v-model="note"
113
119
  class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
114
120
  style="line-height: 1.75;"
@@ -1,17 +1,16 @@
1
1
  import mermaid from 'mermaid/dist/mermaid.esm.mjs'
2
- import { customAlphabet } from 'nanoid'
3
- import { decode } from 'js-base64'
2
+ import { decompressFromBase64 } from 'lz-string'
4
3
  import { clearUndefined } from '@antfu/utils'
5
4
  import setupMermaid from '../setup/mermaid'
5
+ import { makeId } from '../logic/utils'
6
6
 
7
7
  mermaid.startOnLoad = false
8
8
  mermaid.initialize({ startOnLoad: false })
9
9
 
10
- const nanoid = customAlphabet('abcedfghicklmn', 10)
11
10
  const cache = new Map<string, string>()
12
11
 
13
- export async function renderMermaid(encoded: string, options: any) {
14
- const key = encoded + JSON.stringify(options)
12
+ export async function renderMermaid(lzEncoded: string, options: any) {
13
+ const key = lzEncoded + JSON.stringify(options)
15
14
  const _cache = cache.get(key)
16
15
  if (_cache)
17
16
  return _cache
@@ -21,8 +20,8 @@ export async function renderMermaid(encoded: string, options: any) {
21
20
  ...clearUndefined(setupMermaid() || {}),
22
21
  ...clearUndefined(options),
23
22
  })
24
- const code = decode(encoded)
25
- const id = nanoid()
23
+ const code = decompressFromBase64(lzEncoded)
24
+ const id = makeId()
26
25
  const { svg } = await mermaid.render(id, code)
27
26
  cache.set(key, svg)
28
27
  return svg
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.48.0-beta.14",
4
+ "version": "0.48.0-beta.15",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -42,12 +42,11 @@
42
42
  "file-saver": "^2.0.5",
43
43
  "floating-vue": "^5.2.2",
44
44
  "fuse.js": "^7.0.0",
45
- "js-base64": "^3.7.7",
46
45
  "js-yaml": "^4.1.0",
47
46
  "katex": "^0.16.9",
47
+ "lz-string": "^1.5.0",
48
48
  "mermaid": "^10.8.0",
49
49
  "monaco-editor": "^0.37.1",
50
- "nanoid": "^5.0.6",
51
50
  "prettier": "^3.2.5",
52
51
  "recordrtc": "^5.6.2",
53
52
  "resolve": "^1.22.8",
@@ -55,8 +54,8 @@
55
54
  "unocss": "^0.58.5",
56
55
  "vue": "^3.4.20",
57
56
  "vue-router": "^4.3.0",
58
- "@slidev/types": "0.48.0-beta.14",
59
- "@slidev/parser": "0.48.0-beta.14"
57
+ "@slidev/types": "0.48.0-beta.15",
58
+ "@slidev/parser": "0.48.0-beta.15"
60
59
  },
61
60
  "devDependencies": {
62
61
  "vite": "^5.1.4"
@@ -12,7 +12,7 @@ import SlideContainer from '../internals/SlideContainer.vue'
12
12
  import SlideWrapper from '../internals/SlideWrapper'
13
13
  import DrawingPreview from '../internals/DrawingPreview.vue'
14
14
  import IconButton from '../internals/IconButton.vue'
15
- import NoteEditor from '../internals/NoteEditor.vue'
15
+ import NoteEditable from '../internals/NoteEditable.vue'
16
16
  import OverviewClicksSlider from '../internals/OverviewClicksSlider.vue'
17
17
  import { CLICKS_MAX } from '../constants'
18
18
 
@@ -80,6 +80,15 @@ function scrollToSlide(idx: number) {
80
80
  el.scrollIntoView({ behavior: 'smooth', block: 'start' })
81
81
  }
82
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
+
83
92
  onMounted(() => {
84
93
  nextTick(() => {
85
94
  checkActiveBlocks()
@@ -192,7 +201,7 @@ onMounted(() => {
192
201
  <carbon:pen />
193
202
  </IconButton>
194
203
  </div>
195
- <NoteEditor
204
+ <NoteEditable
196
205
  :no="idx"
197
206
  class="max-w-250 w-250 text-lg rounded p3"
198
207
  :auto-height="true"
@@ -200,6 +209,7 @@ onMounted(() => {
200
209
  :clicks-context="getClicksContext(route)"
201
210
  @dblclick="edittingNote !== idx ? edittingNote = idx : null"
202
211
  @update:editing="edittingNote = null"
212
+ @marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
203
213
  />
204
214
  <div
205
215
  v-if="wordCounts[idx] > 0"
@@ -15,7 +15,7 @@ import SlideWrapper from '../internals/SlideWrapper'
15
15
  import SlideContainer from '../internals/SlideContainer.vue'
16
16
  import NavControls from '../internals/NavControls.vue'
17
17
  import QuickOverview from '../internals/QuickOverview.vue'
18
- import NoteEditor from '../internals/NoteEditor.vue'
18
+ import NoteEditable from '../internals/NoteEditable.vue'
19
19
  import NoteStatic from '../internals/NoteStatic.vue'
20
20
  import Goto from '../internals/Goto.vue'
21
21
  import SlidesShow from '../internals/SlidesShow.vue'
@@ -130,7 +130,7 @@ onMounted(() => {
130
130
  <SideEditor />
131
131
  </div>
132
132
  <div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
133
- <NoteEditor
133
+ <NoteEditable
134
134
  v-if="__DEV__"
135
135
  :key="`edit-${currentSlideId}`"
136
136
  v-model:editing="notesEditing"
package/setup/root.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /* __imports__ */
2
2
  import { watch } from 'vue'
3
3
  import { useHead } from '@unhead/vue'
4
- import { nanoid } from 'nanoid'
5
4
  import { configs } from '../env'
6
5
  import { initSharedState, onPatch, patch } from '../state/shared'
7
6
  import { initDrawingState } from '../state/drawings'
@@ -9,6 +8,7 @@ import { clicksContext, currentPage, getPath, isNotesViewer, isPresenter } from
9
8
  import { router } from '../routes'
10
9
  import { TRUST_ORIGINS } from '../constants'
11
10
  import { skipTransition } from '../composables/hmr'
11
+ import { makeId } from '../logic/utils'
12
12
 
13
13
  export default function setupRoot() {
14
14
  // @ts-expect-error injected in runtime
@@ -25,7 +25,7 @@ export default function setupRoot() {
25
25
  initSharedState(`${title} - shared`)
26
26
  initDrawingState(`${title} - drawings`)
27
27
 
28
- const id = `${location.origin}_${nanoid()}`
28
+ const id = `${location.origin}_${makeId()}`
29
29
 
30
30
  // update shared state
31
31
  function updateSharedState() {