@slidev/client 0.48.0-beta.13 → 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>
@@ -69,7 +69,9 @@ export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksConte
69
69
  const thisPath = +(route?.path ?? CLICKS_MAX)
70
70
  const current = computed({
71
71
  get() {
72
- const currentPath = +(currentRoute.value?.path ?? CLICKS_MAX)
72
+ const currentPath = +(currentRoute.value?.path ?? Number.NaN)
73
+ if (!currentPath || Number.isNaN(currentPath))
74
+ return 0
73
75
  if (currentPath === thisPath)
74
76
  return queryClicks.value
75
77
  else if (currentPath > thisPath)
@@ -78,7 +80,7 @@ export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksConte
78
80
  return 0
79
81
  },
80
82
  set(v) {
81
- const currentPath = +(currentRoute.value?.path ?? CLICKS_MAX)
83
+ const currentPath = +(currentRoute.value?.path ?? Number.NaN)
82
84
  if (currentPath === thisPath)
83
85
  queryClicks.value = v
84
86
  },
@@ -2,7 +2,7 @@
2
2
  import { shallowRef } from 'vue'
3
3
  import { showInfoDialog, showOverview, showRecordingDialog } from '../state'
4
4
  import { configs } from '../env'
5
- import SlidesOverview from './SlidesOverview.vue'
5
+ import QuickOverview from './QuickOverview.vue'
6
6
  import InfoDialog from './InfoDialog.vue'
7
7
  import Goto from './Goto.vue'
8
8
 
@@ -15,7 +15,7 @@ if (__SLIDEV_FEATURE_RECORD__) {
15
15
  </script>
16
16
 
17
17
  <template>
18
- <SlidesOverview v-model="showOverview" />
18
+ <QuickOverview v-model="showOverview" />
19
19
  <Goto />
20
20
  <WebCamera v-if="WebCamera" />
21
21
  <RecordingDialog v-if="RecordingDialog" v-model="showRecordingDialog" />
@@ -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" v-bind="$attrs">
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>
@@ -114,13 +114,13 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
114
114
  <IconButton
115
115
  v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__"
116
116
  :title="showEditor ? 'Hide editor' : 'Show editor'"
117
- class="<md:hidden"
117
+ class="lt-md:hidden"
118
118
  @click="showEditor = !showEditor"
119
119
  >
120
120
  <carbon:text-annotation-toggle />
121
121
  </IconButton>
122
122
 
123
- <IconButton v-if="isPresenter" title="Toggle Presenter Layout" @click="togglePresenterLayout">
123
+ <IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial" @click="togglePresenterLayout">
124
124
  <carbon:template />
125
125
  {{ presenterLayout }}
126
126
  </IconButton>
@@ -9,9 +9,13 @@ const props = defineProps<{
9
9
  note?: string
10
10
  placeholder?: string
11
11
  clicksContext?: ClicksContext
12
+ autoScroll?: boolean
12
13
  }>()
13
14
 
14
- defineEmits(['click'])
15
+ const emit = defineEmits<{
16
+ (type: 'markerDblclick', e: MouseEvent, clicks: number): void
17
+ (type: 'markerClick', e: MouseEvent, clicks: number): void
18
+ }>()
15
19
 
16
20
  const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark'))
17
21
  const noteDisplay = ref<HTMLElement | null>(null)
@@ -20,16 +24,13 @@ const CLASS_FADE = 'slidev-note-fade'
20
24
  const CLASS_MARKER = 'slidev-note-click-mark'
21
25
 
22
26
  function highlightNote() {
23
- if (!noteDisplay.value || !withClicks.value || props.clicksContext?.current == null)
27
+ if (!noteDisplay.value || !withClicks.value)
24
28
  return
25
29
 
26
- 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)
27
33
  const disabled = current < 0 || current >= CLICKS_MAX
28
- if (disabled) {
29
- Array.from(noteDisplay.value.querySelectorAll('*'))
30
- .forEach(el => el.classList.remove(CLASS_FADE))
31
- return
32
- }
33
34
 
34
35
  const nodeToIgnores = new Set<Element>()
35
36
  function ignoreParent(node: Element) {
@@ -40,7 +41,6 @@ function highlightNote() {
40
41
  ignoreParent(node.parentElement)
41
42
  }
42
43
 
43
- const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
44
44
  const markersMap = new Map<number, HTMLElement>()
45
45
 
46
46
  // Convert all sibling text nodes to spans, so we attach classes to them
@@ -77,25 +77,39 @@ function highlightNote() {
77
77
 
78
78
  // Apply
79
79
  for (const [count, els] of segments) {
80
- els.forEach(el => el.classList.toggle(
81
- CLASS_FADE,
82
- nodeToIgnores.has(el)
83
- ? false
84
- : count !== current,
85
- ))
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
+ }
86
91
  }
87
92
 
88
93
  for (const [clicks, marker] of markersMap) {
89
94
  marker.classList.remove(CLASS_FADE)
90
- marker.classList.toggle(`${CLASS_MARKER}-past`, clicks < current)
91
- marker.classList.toggle(`${CLASS_MARKER}-active`, clicks === current)
92
- marker.classList.toggle(`${CLASS_MARKER}-next`, clicks === current + 1)
93
- marker.classList.toggle(`${CLASS_MARKER}-future`, clicks > current + 1)
94
- 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
95
103
  props.clicksContext!.current = clicks
96
104
  e.stopPropagation()
97
105
  e.stopImmediatePropagation()
98
- })
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' })
99
113
  }
100
114
  }
101
115
 
@@ -120,23 +134,26 @@ onMounted(() => {
120
134
  ref="noteDisplay"
121
135
  class="prose overflow-auto outline-none slidev-note"
122
136
  :class="[props.class, withClicks ? 'slidev-note-with-clicks' : '']"
123
- @click="$emit('click')"
124
137
  v-html="noteHtml"
125
138
  />
126
139
  <div
127
140
  v-else-if="note"
128
- class="prose overflow-auto outline-none"
141
+ class="prose overflow-auto outline-none slidev-note"
129
142
  :class="props.class"
130
- @click="$emit('click')"
131
143
  >
132
144
  <p v-text="note" />
133
145
  </div>
134
146
  <div
135
147
  v-else
136
- 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"
137
149
  :class="props.class"
138
- @click="$emit('click')"
139
150
  >
140
151
  <p v-text="props.placeholder || 'No notes.'" />
141
152
  </div>
142
153
  </template>
154
+
155
+ <style>
156
+ .slidev-note :first-child {
157
+ margin-top: 0;
158
+ }
159
+ </style>
@@ -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,72 +57,70 @@ 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
- watchEffect(() => {
87
- calculateHeight()
88
- })
89
93
  watch(
90
94
  note,
91
- () => {
92
- nextTick(() => {
93
- calculateHeight()
94
- })
95
- },
96
- { flush: 'post' },
95
+ () => nextTick(() => {
96
+ calculateEditorHeight()
97
+ }),
98
+ { flush: 'post', immediate: true },
97
99
  )
98
100
  </script>
99
101
 
100
102
  <template>
101
103
  <NoteDisplay
102
104
  v-if="!editing"
103
- class="my--4 border-transparent border-2"
105
+ class="border-transparent border-2"
104
106
  :class="[props.class, note ? '' : 'opacity-25 italic select-none']"
105
107
  :style="props.style"
106
108
  :note="note || placeholder"
107
109
  :note-html="info?.noteHTML"
108
110
  :clicks-context="clicksContext"
111
+ :auto-scroll="!autoHeight"
112
+ @marker-click="(e, clicks) => emit('markerClick', e, clicks)"
113
+ @marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
109
114
  />
110
115
  <textarea
111
116
  v-else
112
- ref="input"
117
+ ref="inputEl"
113
118
  v-model="note"
114
119
  class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
115
120
  style="line-height: 1.75;"
116
121
  :style="[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]"
117
122
  :class="props.class"
118
123
  :placeholder="placeholder"
119
- @keydown.esc=" editing = false"
120
- @focus="editing = true"
124
+ @keydown.esc="editing = false"
121
125
  />
122
126
  </template>
@@ -164,18 +164,19 @@ watchEffect(() => {
164
164
  </div>
165
165
  </div>
166
166
  </Transition>
167
- <div v-if="value" class="fixed top-4 right-4 text-gray-400 flex items-center gap-4">
168
- <RouterLink
169
- v-if="__DEV__"
167
+ <div v-if="value" class="fixed top-4 right-4 text-gray-400 flex flex-col items-center gap-2">
168
+ <IconButton title="Close" class="text-2xl" @click="close">
169
+ <carbon:close />
170
+ </IconButton>
171
+ <IconButton
172
+ as="a"
173
+ title="Slides Overview"
170
174
  target="_blank"
171
- to="/overview"
175
+ href="/overview"
172
176
  tab-index="-1"
173
- class="border-main border px3 py1 rounded hover:bg-gray/5 hover:text-primary"
177
+ class="text-2xl"
174
178
  >
175
- List overview
176
- </RouterLink>
177
- <IconButton title="Close" class="text-2xl" @click="close">
178
- <carbon:close />
179
+ <carbon:list-boxes />
179
180
  </IconButton>
180
181
  </div>
181
182
  </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>
@@ -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.13",
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.13",
59
- "@slidev/parser": "0.48.0-beta.13"
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"
package/pages/notes.vue CHANGED
@@ -52,6 +52,7 @@ function decreaseFontSize() {
52
52
  :note-html="currentRoute?.meta?.slide?.noteHTML"
53
53
  :placeholder="`No notes for Slide ${pageNo}.`"
54
54
  :clicks-context="currentRoute?.meta?.__clicksContext"
55
+ :auto-scroll="true"
55
56
  />
56
57
  </div>
57
58
  <div class="flex-none border-t border-main">
@@ -3,7 +3,7 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
3
3
  import { useHead } from '@unhead/vue'
4
4
  import type { RouteRecordRaw } from 'vue-router'
5
5
  import type { ClicksContext } from 'packages/types'
6
- import { themeVars } from '../env'
6
+ import { configs, themeVars } from '../env'
7
7
  import { openInEditor, rawRoutes } from '../logic/nav'
8
8
  import { useFixedClicks } from '../composables/useClicks'
9
9
  import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
@@ -12,14 +12,15 @@ 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
 
19
19
  const cardWidth = 450
20
20
 
21
+ const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
21
22
  useHead({
22
- title: 'List Overview',
23
+ title: `Overview - ${slideTitle}`,
23
24
  })
24
25
 
25
26
  const blocks: Map<number, HTMLElement> = reactive(new Map())
@@ -79,6 +80,15 @@ function scrollToSlide(idx: number) {
79
80
  el.scrollIntoView({ behavior: 'smooth', block: 'start' })
80
81
  }
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
+
82
92
  onMounted(() => {
83
93
  nextTick(() => {
84
94
  checkActiveBlocks()
@@ -191,7 +201,7 @@ onMounted(() => {
191
201
  <carbon:pen />
192
202
  </IconButton>
193
203
  </div>
194
- <NoteEditor
204
+ <NoteEditable
195
205
  :no="idx"
196
206
  class="max-w-250 w-250 text-lg rounded p3"
197
207
  :auto-height="true"
@@ -199,6 +209,7 @@ onMounted(() => {
199
209
  :clicks-context="getClicksContext(route)"
200
210
  @dblclick="edittingNote !== idx ? edittingNote = idx : null"
201
211
  @update:editing="edittingNote = null"
212
+ @marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
202
213
  />
203
214
  <div
204
215
  v-if="wordCounts[idx] > 0"
@@ -56,7 +56,10 @@ const slidesWithNote = computed(() => rawRoutes
56
56
  <div class="flex-auto" />
57
57
  </div>
58
58
  </h2>
59
- <NoteDisplay :note-html="slide!.noteHTML" class="max-w-full" />
59
+ <NoteDisplay
60
+ :note-html="slide!.noteHTML"
61
+ class="max-w-full"
62
+ />
60
63
  </div>
61
64
  <hr v-if="index < slidesWithNote.length - 1" class="border-main mb-8">
62
65
  </div>
@@ -14,8 +14,8 @@ import { useFixedClicks } from '../composables/useClicks'
14
14
  import SlideWrapper from '../internals/SlideWrapper'
15
15
  import SlideContainer from '../internals/SlideContainer.vue'
16
16
  import NavControls from '../internals/NavControls.vue'
17
- import SlidesOverview from '../internals/SlidesOverview.vue'
18
- import NoteEditor from '../internals/NoteEditor.vue'
17
+ import QuickOverview from '../internals/QuickOverview.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'
@@ -45,12 +45,19 @@ const nextFrame = computed(() => {
45
45
  else
46
46
  return null
47
47
  })
48
+
48
49
  const nextFrameClicksCtx = computed(() => {
49
50
  return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
50
51
  })
51
- watch([currentRoute, queryClicks], () => {
52
- nextFrameClicksCtx.value && (nextFrameClicksCtx.value.current = nextFrame.value![1])
53
- }, { immediate: true })
52
+
53
+ watch(
54
+ [currentRoute, queryClicks],
55
+ () => {
56
+ if (nextFrameClicksCtx.value)
57
+ nextFrameClicksCtx.value.current = nextFrame.value![1]
58
+ },
59
+ { immediate: true },
60
+ )
54
61
 
55
62
  const SideEditor = shallowRef<any>()
56
63
  if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
@@ -86,21 +93,6 @@ onMounted(() => {
86
93
  <template>
87
94
  <div class="bg-main h-full slidev-presenter">
88
95
  <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
96
  <div ref="main" class="relative grid-section main flex flex-col p-2 lg:p-4" :style="themeVars">
105
97
  <SlideContainer
106
98
  key="main"
@@ -110,8 +102,8 @@ onMounted(() => {
110
102
  <SlidesShow render-context="presenter" />
111
103
  </template>
112
104
  </SlideContainer>
113
- <div class="context">
114
- current
105
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
106
+ Current
115
107
  </div>
116
108
  </div>
117
109
  <div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
@@ -129,8 +121,8 @@ onMounted(() => {
129
121
  render-context="previewNext"
130
122
  />
131
123
  </SlideContainer>
132
- <div class="context">
133
- next
124
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
125
+ Next
134
126
  </div>
135
127
  </div>
136
128
  <!-- Notes -->
@@ -138,12 +130,12 @@ onMounted(() => {
138
130
  <SideEditor />
139
131
  </div>
140
132
  <div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
141
- <NoteEditor
133
+ <NoteEditable
142
134
  v-if="__DEV__"
143
135
  :key="`edit-${currentSlideId}`"
136
+ v-model:editing="notesEditing"
144
137
  :no="currentSlideId"
145
138
  class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
146
- :editing="notesEditing"
147
139
  :clicks-context="clicksContext"
148
140
  :style="{ fontSize: `${presenterNotesFontSize}em` }"
149
141
  />
@@ -171,8 +163,20 @@ onMounted(() => {
171
163
  </IconButton>
172
164
  </div>
173
165
  </div>
174
- <div class="grid-section bottom">
166
+ <div class="grid-section bottom flex">
175
167
  <NavControls :persist="true" />
168
+ <div flex-auto />
169
+ <div
170
+ class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
171
+ opacity="50 hover:100"
172
+ @click="resetTimer"
173
+ >
174
+ <carbon:time class="absolute" />
175
+ <carbon:renew class="absolute opacity-0" />
176
+ </div>
177
+ <div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
178
+ {{ timer }}
179
+ </div>
176
180
  </div>
177
181
  <DrawingControls v-if="__SLIDEV_FEATURE_DRAWINGS__" />
178
182
  </div>
@@ -184,7 +188,7 @@ onMounted(() => {
184
188
  </div>
185
189
  </div>
186
190
  <Goto />
187
- <SlidesOverview v-model="showOverview" />
191
+ <QuickOverview v-model="showOverview" />
188
192
  </template>
189
193
 
190
194
  <style scoped>
@@ -204,7 +208,7 @@ onMounted(() => {
204
208
  }
205
209
 
206
210
  .grid-container {
207
- --uno: bg-active;
211
+ --uno: bg-gray/20;
208
212
  height: 100%;
209
213
  width: 100%;
210
214
  display: grid;
@@ -213,9 +217,8 @@ onMounted(() => {
213
217
 
214
218
  .grid-container.layout1 {
215
219
  grid-template-columns: 1fr 1fr;
216
- grid-template-rows: min-content 2fr 1fr min-content;
220
+ grid-template-rows: 2fr 1fr min-content;
217
221
  grid-template-areas:
218
- 'top top'
219
222
  'main main'
220
223
  'note next'
221
224
  'bottom bottom';
@@ -223,9 +226,8 @@ onMounted(() => {
223
226
 
224
227
  .grid-container.layout2 {
225
228
  grid-template-columns: 3fr 2fr;
226
- grid-template-rows: min-content 2fr 1fr min-content;
229
+ grid-template-rows: 2fr 1fr min-content;
227
230
  grid-template-areas:
228
- 'top top'
229
231
  'note main'
230
232
  'note next'
231
233
  'bottom bottom';
@@ -234,9 +236,8 @@ onMounted(() => {
234
236
  @media (max-aspect-ratio: 3/5) {
235
237
  .grid-container.layout1 {
236
238
  grid-template-columns: 1fr;
237
- grid-template-rows: min-content 1fr 1fr 1fr min-content;
239
+ grid-template-rows: 1fr 1fr 1fr min-content;
238
240
  grid-template-areas:
239
- 'top'
240
241
  'main'
241
242
  'note'
242
243
  'next'
@@ -247,9 +248,8 @@ onMounted(() => {
247
248
  @media (min-aspect-ratio: 1/1) {
248
249
  .grid-container.layout1 {
249
250
  grid-template-columns: 1fr 1.1fr 0.9fr;
250
- grid-template-rows: min-content 1fr 2fr min-content;
251
+ grid-template-rows: 1fr 2fr min-content;
251
252
  grid-template-areas:
252
- 'top top top'
253
253
  'main main next'
254
254
  'main main note'
255
255
  'bottom bottom bottom';
@@ -279,10 +279,4 @@ onMounted(() => {
279
279
  .grid-section.bottom {
280
280
  grid-area: bottom;
281
281
  }
282
- .context {
283
- position: absolute;
284
- top: 0;
285
- left: 0;
286
- --uno: px-1 text-xs bg-gray-400 bg-opacity-50 opacity-75 rounded-br-md;
287
- }
288
282
  </style>
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() {