@slidev/client 0.49.4 → 0.49.6

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,6 +9,8 @@ Simple Arrow
9
9
  -->
10
10
 
11
11
  <script setup lang="ts">
12
+ import { ref } from 'vue'
13
+ import { onClickOutside } from '@vueuse/core'
12
14
  import { makeId } from '../logic/utils'
13
15
 
14
16
  defineProps<{
@@ -18,38 +20,53 @@ defineProps<{
18
20
  y2: number | string
19
21
  width?: number | string
20
22
  color?: string
23
+ twoWay?: boolean
21
24
  }>()
22
25
 
26
+ const emit = defineEmits(['dblclick', 'clickOutside'])
27
+
23
28
  const id = makeId()
29
+
30
+ const markerAttrs = {
31
+ markerUnits: 'strokeWidth',
32
+ markerHeight: 7,
33
+ refY: 3.5,
34
+ orient: 'auto',
35
+ }
36
+
37
+ const clickArea = ref<HTMLElement>()
38
+ onClickOutside(clickArea, () => emit('clickOutside'))
24
39
  </script>
25
40
 
26
41
  <template>
27
42
  <svg
28
- class="absolute left-0 top-0 pointer-events-none"
43
+ class="absolute left-0 top-0"
29
44
  :width="Math.max(+x1, +x2) + 50"
30
45
  :height="Math.max(+y1, +y2) + 50"
31
46
  >
32
47
  <defs>
33
- <marker
34
- :id="id"
35
- markerUnits="strokeWidth"
36
- :markerWidth="10"
37
- :markerHeight="7"
38
- refX="9"
39
- refY="3.5"
40
- orient="auto"
41
- >
42
- <polygon points="0 0, 10 3.5, 0 7" :fill="color || 'currentColor'" />
48
+ <marker :id="id" markerWidth="10" refX="9" v-bind="markerAttrs">
49
+ <polygon points="0 0, 10 3.5, 0 7" :fill="color || 'currentColor'" @dblclick="emit('dblclick')" />
50
+ </marker>
51
+ <marker v-if="twoWay" :id="`${id}-rev`" markerWidth="20" refX="11" v-bind="markerAttrs">
52
+ <polygon points="20 0, 10 3.5, 20 7" :fill="color || 'currentColor'" @dblclick="emit('dblclick')" />
43
53
  </marker>
44
54
  </defs>
45
55
  <line
46
- :x1="+x1"
47
- :y1="+y1"
48
- :x2="+x2"
49
- :y2="+y2"
56
+ :x1 :y1 :x2 :y2
50
57
  :stroke="color || 'currentColor'"
51
58
  :stroke-width="width || 2"
52
59
  :marker-end="`url(#${id})`"
60
+ :marker-start="twoWay ? `url(#${id}-rev)` : 'none'"
61
+ @dblclick="emit('dblclick')"
62
+ />
63
+ <line
64
+ ref="clickArea"
65
+ :x1 :y1 :x2 :y2
66
+ stroke="transparent"
67
+ stroke-linecap="round"
68
+ :stroke-width="20"
69
+ @dblclick="emit('dblclick')"
53
70
  />
54
71
  </svg>
55
72
  </template>
@@ -33,6 +33,7 @@ const props = withDefaults(defineProps<{
33
33
  editorOptions?: monaco.editor.IEditorOptions
34
34
  ata?: boolean
35
35
  runnable?: boolean
36
+ writable?: string
36
37
  autorun?: boolean | 'once'
37
38
  showOutputAt?: RawAtValue
38
39
  outputHeight?: string
@@ -52,6 +53,7 @@ const props = withDefaults(defineProps<{
52
53
 
53
54
  const code = ref(lz.decompressFromBase64(props.codeLz).trimEnd())
54
55
  const diff = props.diffLz && ref(lz.decompressFromBase64(props.diffLz).trimEnd())
56
+ const isWritable = computed(() => props.writable && !props.readonly && __DEV__)
55
57
 
56
58
  const langMap: Record<string, string> = {
57
59
  ts: 'typescript',
@@ -95,7 +97,7 @@ const stopWatchTypesLoading = whenever(
95
97
  onMounted(async () => {
96
98
  // Lazy load monaco, so it will be bundled in async chunk
97
99
  const { default: setup } = await import('../setup/monaco')
98
- const { ata, monaco } = await setup()
100
+ const { ata, monaco, editorOptions } = await setup()
99
101
  const model = monaco.editor.createModel(code.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
100
102
  model.onDidChangeContent(() => code.value = model.getValue())
101
103
  const commonOptions = {
@@ -112,6 +114,7 @@ onMounted(async () => {
112
114
  fontSize: 11.5,
113
115
  fontFamily: 'var(--slidev-code-font-family)',
114
116
  scrollBeyondLastLine: false,
117
+ ...editorOptions,
115
118
  ...props.editorOptions,
116
119
  } satisfies monaco.editor.IStandaloneEditorConstructionOptions & monaco.editor.IDiffEditorConstructionOptions
117
120
 
@@ -151,6 +154,7 @@ onMounted(async () => {
151
154
  contentHeight.value = newHeight
152
155
  nextTick(() => editableEditor.layout())
153
156
  })
157
+
154
158
  editableEditor = editor
155
159
  }
156
160
  loadTypes.value = () => {
@@ -173,6 +177,23 @@ onMounted(async () => {
173
177
  : /* BELOW */ `` // reset
174
178
  }
175
179
  }
180
+
181
+ editableEditor.addAction({
182
+ id: 'slidev-save',
183
+ label: 'Save',
184
+ keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
185
+ run: () => {
186
+ if (!isWritable.value || !import.meta.hot?.send) {
187
+ console.warn('[Slidev] this monaco editor is not writable, save action is ignored.')
188
+ return
189
+ }
190
+ import.meta.hot.send('slidev:monaco-write', {
191
+ file: props.writable!,
192
+ content: editableEditor.getValue(),
193
+ })
194
+ },
195
+ })
196
+
176
197
  nextTick(() => monaco.editor.remeasureFonts())
177
198
  })
178
199
  </script>
@@ -1,18 +1,33 @@
1
1
  <script setup lang="ts">
2
2
  import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
3
3
  import type { KeyedTokensInfo } from 'shiki-magic-move/types'
4
+ import type { PropType } from 'vue'
4
5
  import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
5
6
  import lz from 'lz-string'
6
7
  import { useSlideContext } from '../context'
7
8
  import { makeId, updateCodeHighlightRange } from '../logic/utils'
8
9
  import { useNav } from '../composables/useNav'
9
10
  import { CLICKS_MAX } from '../constants'
11
+ import { configs } from '../env'
10
12
 
11
- const props = defineProps<{
12
- at?: string | number
13
- stepsLz: string
14
- stepRanges: string[][]
15
- }>()
13
+ const props = defineProps({
14
+ at: {
15
+ type: [String, Number],
16
+ default: '+1',
17
+ },
18
+ stepsLz: {
19
+ type: String,
20
+ required: true,
21
+ },
22
+ stepRanges: {
23
+ type: Array as PropType<string[][]>,
24
+ required: true,
25
+ },
26
+ lines: {
27
+ type: Boolean,
28
+ default: configs.lineNumbers,
29
+ },
30
+ })
16
31
 
17
32
  const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
18
33
  const { $clicksContext: clicks, $scale: scale, $zoom: zoom } = useSlideContext()
@@ -37,7 +52,7 @@ onMounted(() => {
37
52
  throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')
38
53
 
39
54
  const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)
40
- const clickInfo = clicks.calculateSince(props.at ?? '+1', clickCounts - 1)
55
+ const clickInfo = clicks.calculateSince(props.at, clickCounts - 1)
41
56
  clicks.register(id, clickInfo)
42
57
 
43
58
  watch(
package/builtin/VDrag.vue CHANGED
@@ -8,7 +8,7 @@ const props = defineProps<{
8
8
  markdownSource?: DragElementMarkdownSource
9
9
  }>()
10
10
 
11
- const { id, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
11
+ const { dragId, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
12
12
 
13
13
  onMounted(mounted)
14
14
  onUnmounted(unmounted)
@@ -17,7 +17,7 @@ onUnmounted(unmounted)
17
17
  <template>
18
18
  <div
19
19
  ref="container"
20
- :data-drag-id="id"
20
+ :data-drag-id="dragId"
21
21
  :style="containerStyle"
22
22
  class="p-1"
23
23
  @dblclick="startDragging"
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, onUnmounted } from 'vue'
3
+ import type { DragElementMarkdownSource } from '../composables/useDragElements'
4
+ import { useDragElement } from '../composables/useDragElements'
5
+ import Arrow from './Arrow.vue'
6
+
7
+ const props = defineProps<{
8
+ pos?: string
9
+ markdownSource?: DragElementMarkdownSource
10
+ width?: number | string
11
+ color?: string
12
+ twoWay?: boolean
13
+ }>()
14
+
15
+ const { dragId, mounted, unmounted, startDragging, stopDragging, x0, y0, width, height } = useDragElement(null, props.pos ?? '100,100,300,300', props.markdownSource, true)
16
+
17
+ onMounted(mounted)
18
+ onUnmounted(unmounted)
19
+
20
+ const x1 = computed(() => x0.value - width.value / 2)
21
+ const y1 = computed(() => y0.value - height.value / 2)
22
+ const x2 = computed(() => x0.value + width.value / 2)
23
+ const y2 = computed(() => y0.value + height.value / 2)
24
+ </script>
25
+
26
+ <template>
27
+ <Arrow
28
+ :x1 :y1 :x2 :y2
29
+ :data-drag-id="dragId"
30
+ :width="props.width"
31
+ :color="props.color"
32
+ :two-way="props.twoWay"
33
+ @dblclick="startDragging"
34
+ @click-outside="stopDragging"
35
+ />
36
+ </template>
@@ -68,7 +68,8 @@ export function useDragElementsUpdater(no: number) {
68
68
  let replaced = false
69
69
 
70
70
  section = type === 'prop'
71
- ? section.replace(/<(v-?drag)(.*?)>/gi, (full, tag, attrs, index) => {
71
+ // eslint-disable-next-line regexp/no-super-linear-backtracking
72
+ ? section.replace(/<(v-?drag-?\w*)(.*?)>/gi, (full, tag, attrs, index) => {
72
73
  if (index === idx) {
73
74
  replaced = true
74
75
  const posMatch = attrs.match(/pos=".*?"/)
@@ -112,7 +113,7 @@ export function useDragElementsUpdater(no: number) {
112
113
  }
113
114
  }
114
115
 
115
- export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource) {
116
+ export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource, isArrow?: boolean) {
116
117
  function inject<T>(key: InjectionKey<T> | string): T | undefined {
117
118
  return directive
118
119
  ? directiveInject(directive, key)
@@ -129,7 +130,7 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
129
130
  const enabled = ['slide', 'presenter'].includes(renderContext.value)
130
131
 
131
132
  let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'
132
- let id: string = makeId()
133
+ let dragId: string = makeId()
133
134
  let pos: number[] | undefined
134
135
  if (Array.isArray(posRaw)) {
135
136
  pos = posRaw
@@ -139,8 +140,8 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
139
140
  }
140
141
  else if (posRaw != null) {
141
142
  dataSource = 'frontmatter'
142
- id = `${posRaw}`
143
- posRaw = frontmatter?.dragPos?.[id]
143
+ dragId = `${posRaw}`
144
+ posRaw = frontmatter?.dragPos?.[dragId]
144
145
  pos = (posRaw as string)?.split(',').map(Number)
145
146
  }
146
147
 
@@ -149,12 +150,12 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
149
150
 
150
151
  const watchStopHandles: WatchStopHandle[] = [stopWatchBounds]
151
152
 
152
- const autoHeight = posRaw != null && !Number.isFinite(pos?.[3])
153
+ const autoHeight = !isArrow && posRaw != null && !Number.isFinite(pos?.[3])
153
154
  pos ??= [Number.NaN, Number.NaN, 0]
154
155
  const width = ref(pos[2])
155
156
  const x0 = ref(pos[0] + pos[2] / 2)
156
157
 
157
- const rotate = ref(pos[4] ?? 0)
158
+ const rotate = ref(isArrow ? 0 : (pos[4] ?? 0))
158
159
  const rotateRad = computed(() => rotate.value * Math.PI / 180)
159
160
  const rotateSin = computed(() => Math.sin(rotateRad.value))
160
161
  const rotateCos = computed(() => Math.cos(rotateRad.value))
@@ -163,7 +164,9 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
163
164
  const bounds = ref({ left: 0, top: 0, width: 0, height: 0 })
164
165
  const actualHeight = ref(0)
165
166
  function updateBounds() {
166
- const rect = container.value!.getBoundingClientRect()
167
+ if (!container.value)
168
+ return
169
+ const rect = container.value.getBoundingClientRect()
167
170
  bounds.value = {
168
171
  left: rect.left / zoom.value,
169
172
  top: rect.top / zoom.value,
@@ -175,32 +178,36 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
175
178
  watchStopHandles.push(watch(width, updateBounds, { flush: 'post' }))
176
179
 
177
180
  const configuredHeight = ref(pos[3] ?? 0)
178
- const height = computed({
179
- get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
180
- set: v => !autoHeight && (configuredHeight.value = v),
181
- })
182
- const configuredY0 = ref(pos[1])
183
- const y0 = computed({
184
- get: () => configuredY0.value + height.value / 2,
185
- set: v => configuredY0.value = v - height.value / 2,
186
- })
187
-
188
- const containerStyle = computed<CSSProperties>(() => {
181
+ const height = autoHeight
182
+ ? computed({
183
+ get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
184
+ set: v => !autoHeight && (configuredHeight.value = v),
185
+ })
186
+ : configuredHeight
187
+ const configuredY0 = autoHeight ? ref(pos[1]) : ref(pos[1] + pos[3] / 2)
188
+ const y0 = autoHeight
189
+ ? computed({
190
+ get: () => configuredY0.value + height.value / 2,
191
+ set: v => configuredY0.value = v - height.value / 2,
192
+ })
193
+ : configuredY0
194
+
195
+ const containerStyle = computed(() => {
189
196
  return Number.isFinite(x0.value)
190
197
  ? {
191
- position: 'absolute',
192
- zIndex: 100,
193
- left: `${x0.value - width.value / 2}px`,
194
- top: `${y0.value - height.value / 2}px`,
195
- width: `${width.value}px`,
196
- height: autoHeight ? undefined : `${height.value}px`,
197
- transformOrigin: 'center center',
198
- transform: `rotate(${rotate.value}deg)`,
199
- }
198
+ position: 'absolute',
199
+ zIndex: 100,
200
+ left: `${x0.value - width.value / 2}px`,
201
+ top: `${y0.value - height.value / 2}px`,
202
+ width: `${width.value}px`,
203
+ height: autoHeight ? undefined : `${height.value}px`,
204
+ transformOrigin: 'center center',
205
+ transform: `rotate(${rotate.value}deg)`,
206
+ } satisfies CSSProperties
200
207
  : {
201
- position: 'absolute',
202
- zIndex: 100,
203
- }
208
+ position: 'absolute',
209
+ zIndex: 100,
210
+ } satisfies CSSProperties
204
211
  })
205
212
 
206
213
  watchStopHandles.push(
@@ -218,15 +225,16 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
218
225
  if (dataSource === 'directive')
219
226
  posStr = `[${posStr}]`
220
227
 
221
- updater.value(id, posStr, dataSource, markdownSource)
228
+ updater.value(dragId, posStr, dataSource, markdownSource)
222
229
  },
223
230
  ),
224
231
  )
225
232
 
226
233
  const state = {
227
- id,
234
+ dragId,
228
235
  dataSource,
229
236
  markdownSource,
237
+ isArrow,
230
238
  zoom,
231
239
  autoHeight,
232
240
  x0,
package/env.ts CHANGED
@@ -17,4 +17,6 @@ export const themeVars = computed(() => {
17
17
  return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
18
18
  })
19
19
 
20
- export const slidesTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
20
+ export const slidesTitle = configs.slidesTitle
21
+
22
+ export const pathPrefix = import.meta.env.BASE_URL + (__SLIDEV_HASH_ROUTE__ ? '#/' : '')
@@ -10,14 +10,14 @@ import { slideHeight, slideWidth } from '../env'
10
10
  import { magicKeys } from '../state'
11
11
 
12
12
  const { data } = defineProps<{ data: DragElementState }>()
13
- const { id, zoom, autoHeight, x0, y0, width, height, rotate } = data
13
+ const { dragId, zoom, autoHeight, x0, y0, width, height, rotate, isArrow } = data
14
14
 
15
15
  const slideScale = inject(injectionSlideScale, ref(1))
16
16
  const scale = computed(() => slideScale.value * zoom.value)
17
17
  const { left: slideLeft, top: slideTop } = useSlideBounds()
18
18
 
19
19
  const ctrlSize = 10
20
- const minSize = 40
20
+ const minSize = isArrow ? Number.NEGATIVE_INFINITY : 40
21
21
  const minRemain = 10
22
22
 
23
23
  const rotateRad = computed(() => rotate.value * Math.PI / 180)
@@ -32,6 +32,9 @@ const boundingTop = computed(() => y0.value - boundingHeight.value / 2)
32
32
  const boundingRight = computed(() => x0.value + boundingWidth.value / 2)
33
33
  const boundingBottom = computed(() => y0.value + boundingHeight.value / 2)
34
34
 
35
+ const arrowRevX = computed(() => isArrow && width.value < 0)
36
+ const arrowRevY = computed(() => isArrow && height.value < 0)
37
+
35
38
  let currentDrag: {
36
39
  x0: number
37
40
  y0: number
@@ -206,11 +209,12 @@ function getCornerProps(isLeft: boolean, isTop: boolean) {
206
209
  width: `${ctrlSize}px`,
207
210
  height: `${ctrlSize}px`,
208
211
  margin: `-${ctrlSize / 2}px`,
209
- left: isLeft ? '0' : undefined,
210
- right: isLeft ? undefined : '0',
211
- top: isTop ? '0' : undefined,
212
- bottom: isTop ? undefined : '0',
213
- cursor: +isLeft + +isTop === 1 ? 'nesw-resize' : 'nwse-resize',
212
+ left: isLeft !== arrowRevX.value ? '0' : undefined,
213
+ right: isLeft !== arrowRevX.value ? undefined : '0',
214
+ top: isTop !== arrowRevY.value ? '0' : undefined,
215
+ bottom: isTop !== arrowRevY.value ? undefined : '0',
216
+ cursor: isArrow ? 'move' : +isLeft + +isTop === 1 ? 'nesw-resize' : 'nwse-resize',
217
+ borderRadius: isArrow ? '50%' : undefined,
214
218
  },
215
219
  class: ctrlClasses,
216
220
  }
@@ -356,14 +360,14 @@ watchEffect(() => {
356
360
  <div
357
361
  v-if="Number.isFinite(x0)"
358
362
  id="drag-control-container"
359
- :data-drag-id="id"
363
+ :data-drag-id="dragId"
360
364
  :style="{
361
365
  position: 'absolute',
362
366
  zIndex: 100,
363
- left: `${zoom * (x0 - width / 2)}px`,
364
- top: `${zoom * (y0 - height / 2)}px`,
365
- width: `${zoom * width}px`,
366
- height: `${zoom * height}px`,
367
+ left: `${zoom * (x0 - Math.abs(width) / 2)}px`,
368
+ top: `${zoom * (y0 - Math.abs(height) / 2)}px`,
369
+ width: `${zoom * Math.abs(width)}px`,
370
+ height: `${zoom * Math.abs(height)}px`,
367
371
  transformOrigin: 'center center',
368
372
  transform: `rotate(${rotate}deg)`,
369
373
  }"
@@ -371,27 +375,31 @@ watchEffect(() => {
371
375
  @pointermove="onPointermove"
372
376
  @pointerup="onPointerup"
373
377
  >
374
- <div class="absolute inset-0 z-100 b b-dark dark:b-gray-400">
378
+ <div class="absolute inset-0 z-100 dark:b-gray-400" :class="isArrow ? '' : 'b b-dark'">
375
379
  <template v-if="!autoHeight">
376
380
  <div v-bind="getCornerProps(true, true)" />
377
- <div v-bind="getCornerProps(true, false)" />
378
- <div v-bind="getCornerProps(false, true)" />
379
381
  <div v-bind="getCornerProps(false, false)" />
382
+ <template v-if="!isArrow">
383
+ <div v-bind="getCornerProps(true, false)" />
384
+ <div v-bind="getCornerProps(false, true)" />
385
+ </template>
380
386
  </template>
381
- <div v-bind="getBorderProps('l')" />
382
- <div v-bind="getBorderProps('r')" />
383
- <template v-if="!autoHeight">
384
- <div v-bind="getBorderProps('t')" />
385
- <div v-bind="getBorderProps('b')" />
387
+ <template v-if="!isArrow">
388
+ <div v-bind="getBorderProps('l')" />
389
+ <div v-bind="getBorderProps('r')" />
390
+ <template v-if="!autoHeight">
391
+ <div v-bind="getBorderProps('t')" />
392
+ <div v-bind="getBorderProps('b')" />
393
+ </template>
394
+ <div v-bind="getRotateProps()" />
395
+ <div
396
+ class="absolute -top-15px w-0 b b-dashed b-dark dark:b-gray-400"
397
+ :style="{
398
+ left: 'calc(50% - 1px)',
399
+ height: autoHeight ? '14px' : '10px',
400
+ }"
401
+ />
386
402
  </template>
387
- <div v-bind="getRotateProps()" />
388
- <div
389
- class="absolute -top-15px w-0 b b-dashed b-dark dark:b-gray-400"
390
- :style="{
391
- left: 'calc(50% - 1px)',
392
- height: autoHeight ? '14px' : '10px',
393
- }"
394
- />
395
403
  </div>
396
404
  </div>
397
405
  </template>
@@ -135,35 +135,35 @@ watch(activeElement, () => {
135
135
  @input="updateText"
136
136
  >
137
137
  </div>
138
- <ul
138
+ <div
139
139
  v-if="result.length > 0"
140
140
  ref="list"
141
141
  class="autocomplete-list"
142
142
  shadow="~"
143
143
  border="~ transparent rounded dark:main"
144
144
  >
145
- <li
146
- v-for="(item, index) of result"
147
- ref="items"
148
- :key="item.id"
149
- role="button"
150
- tabindex="0"
151
- p="x-4 y-2"
152
- cursor-pointer
153
- hover="op100"
154
- flex="~ gap-2"
155
- w-90
156
- items-center
157
- :border="index === 0 ? '' : 't main'"
158
- :class="selectedIndex === index ? 'bg-active op100' : 'op80'"
159
- @click.stop.prevent="select(item.no)"
160
- >
161
- <div w-4 text-right op50 text-sm>
162
- {{ item.no }}
163
- </div>
164
- <TitleRenderer :no="item.no" />
165
- </li>
166
- </ul>
145
+ <ul table w-full border-collapse>
146
+ <li
147
+ v-for="(item, index) of result"
148
+ ref="items"
149
+ :key="item.id"
150
+ role="button"
151
+ tabindex="0"
152
+ cursor-pointer
153
+ hover="op100"
154
+ table-row
155
+ items-center
156
+ :border="index === 0 ? undefined : 't main'"
157
+ :class="selectedIndex === index ? 'bg-active op100' : 'op80'"
158
+ @click.stop.prevent="select(item.no)"
159
+ >
160
+ <div text-right op50 text-sm table-cell py-2 pl-4 pr-3 vertical-middle>
161
+ {{ item.no }}
162
+ </div>
163
+ <TitleRenderer table-cell py-2 pr-4 w-full :no="item.no" />
164
+ </li>
165
+ </ul>
166
+ </div>
167
167
  </div>
168
168
  </template>
169
169
 
@@ -6,9 +6,7 @@ import { configs, slideHeight, slideWidth } from '../env'
6
6
  import { getSlideClass } from '../utils'
7
7
  import type { SlidevContextNav } from '../composables/useNav'
8
8
  import SlideWrapper from './SlideWrapper.vue'
9
-
10
- import GlobalTop from '#slidev/global-components/top'
11
- import GlobalBottom from '#slidev/global-components/bottom'
9
+ import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
12
10
 
13
11
  const { nav } = defineProps<{
14
12
  nav: SlidevContextNav
@@ -6,6 +6,7 @@ import { currentOverviewPage, overviewRowCount } from '../logic/overview'
6
6
  import { createFixedClicks } from '../composables/useClicks'
7
7
  import { CLICKS_MAX } from '../constants'
8
8
  import { useNav } from '../composables/useNav'
9
+ import { pathPrefix } from '../env'
9
10
  import SlideContainer from './SlideContainer.vue'
10
11
  import SlideWrapper from './SlideWrapper.vue'
11
12
  import DrawingPreview from './DrawingPreview.vue'
@@ -169,7 +170,7 @@ setTimeout(() => {
169
170
  as="a"
170
171
  title="Slides Overview"
171
172
  target="_blank"
172
- href="/overview"
173
+ :href="`${pathPrefix}overview`"
173
174
  tab-index="-1"
174
175
  class="text-2xl"
175
176
  >
@@ -7,6 +7,7 @@ import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, i
7
7
  import { getSlideClass } from '../utils'
8
8
  import { configs } from '../env'
9
9
  import SlideLoading from './SlideLoading.vue'
10
+ import { SlideBottom, SlideTop } from '#slidev/global-layers'
10
11
 
11
12
  const props = defineProps({
12
13
  clicksContext: {
@@ -67,7 +68,13 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
67
68
  :class="getSlideClass(route, ['slide', 'presenter'].includes(props.renderContext) ? '' : 'disable-view-transition')"
68
69
  :style="style"
69
70
  >
71
+ <div v-if="SlideBottom" class="absolute inset-0">
72
+ <SlideBottom />
73
+ </div>
70
74
  <SlideComponent />
75
+ <div v-if="SlideTop" class="absolute inset-0">
76
+ <SlideTop />
77
+ </div>
71
78
  </div>
72
79
  </template>
73
80
 
@@ -9,9 +9,7 @@ import { activeDragElement } from '../state'
9
9
  import { CLICKS_MAX } from '../constants'
10
10
  import SlideWrapper from './SlideWrapper.vue'
11
11
  import DragControl from './DragControl.vue'
12
-
13
- import GlobalTop from '#slidev/global-components/top'
14
- import GlobalBottom from '#slidev/global-components/bottom'
12
+ import { GlobalBottom, GlobalTop } from '#slidev/global-layers'
15
13
 
16
14
  defineProps<{
17
15
  renderContext: 'slide' | 'presenter'
package/logic/slides.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SlideRoute } from '@slidev/types'
2
+ import { pathPrefix } from '../env'
2
3
  import { slides } from '#slidev/slides'
3
4
 
4
5
  export { slides }
@@ -16,5 +17,5 @@ export function getSlidePath(
16
17
  if (typeof route === 'number' || typeof route === 'string')
17
18
  route = getSlide(route)!
18
19
  const no = route.meta.slide?.frontmatter.routeAlias ?? route.no
19
- return presenter ? `/presenter/${no}` : `/${no}`
20
+ return presenter ? `${pathPrefix}presenter/${no}` : `${pathPrefix}${no}`
20
21
  }
package/modules/v-drag.ts CHANGED
@@ -18,12 +18,12 @@ export function createVDragDirective() {
18
18
  }
19
19
  state.container.value = el
20
20
  el.draggingState = state
21
- el.dataset.dragId = state.id
21
+ el.dataset.dragId = state.dragId
22
22
  state.watchStopHandles.push(
23
23
  watch(state.containerStyle, (style) => {
24
24
  for (const [k, v] of Object.entries(style)) {
25
25
  if (v)
26
- el.style[k as any] = v
26
+ el.style[k as any] = v as any
27
27
  }
28
28
  }, { immediate: true }),
29
29
  )
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.49.4",
4
+ "version": "0.49.6",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -32,14 +32,14 @@
32
32
  "@iconify-json/carbon": "^1.1.34",
33
33
  "@iconify-json/ph": "^1.1.13",
34
34
  "@iconify-json/svg-spinners": "^1.1.2",
35
- "@shikijs/monaco": "^1.6.0",
36
- "@shikijs/vitepress-twoslash": "^1.6.0",
35
+ "@shikijs/monaco": "^1.6.1",
36
+ "@shikijs/vitepress-twoslash": "^1.6.1",
37
37
  "@slidev/rough-notation": "^0.1.0",
38
38
  "@typescript/ata": "^0.9.4",
39
39
  "@unhead/vue": "^1.9.11",
40
40
  "@unocss/reset": "^0.60.3",
41
- "@vueuse/core": "^10.9.0",
42
- "@vueuse/math": "^10.9.0",
41
+ "@vueuse/core": "^10.10.0",
42
+ "@vueuse/math": "^10.10.0",
43
43
  "@vueuse/motion": "^2.1.0",
44
44
  "codemirror": "^5.65.16",
45
45
  "drauu": "^0.4.0",
@@ -52,17 +52,17 @@
52
52
  "monaco-editor": "^0.49.0",
53
53
  "prettier": "^3.2.5",
54
54
  "recordrtc": "^5.6.2",
55
- "shiki": "^1.6.0",
55
+ "shiki": "^1.6.1",
56
56
  "shiki-magic-move": "^0.4.2",
57
57
  "typescript": "^5.4.5",
58
58
  "unocss": "^0.60.3",
59
59
  "vue": "^3.4.27",
60
60
  "vue-router": "^4.3.2",
61
61
  "yaml": "^2.4.2",
62
- "@slidev/types": "0.49.4",
63
- "@slidev/parser": "0.49.4"
62
+ "@slidev/parser": "0.49.6",
63
+ "@slidev/types": "0.49.6"
64
64
  },
65
65
  "devDependencies": {
66
- "vite": "^5.2.11"
66
+ "vite": "^5.2.12"
67
67
  }
68
68
  }
package/setup/monaco.ts CHANGED
@@ -95,10 +95,10 @@ const setup = createSingletonPromise(async () => {
95
95
  const { shiki, themes, shikiToMonaco } = await import('#slidev/shiki')
96
96
  const highlighter = await shiki
97
97
 
98
- const setupReturn: MonacoSetupReturn = {}
98
+ const editorOptions: MonacoSetupReturn['editorOptions'] & object = {}
99
99
  for (const setup of setups) {
100
100
  const result = await setup(monaco)
101
- Object.assign(setupReturn, result)
101
+ Object.assign(editorOptions, result?.editorOptions)
102
102
  }
103
103
 
104
104
  // Use Shiki to highlight Monaco
@@ -117,14 +117,25 @@ const setup = createSingletonPromise(async () => {
117
117
  return {
118
118
  monaco,
119
119
  ata,
120
- ...setupReturn,
120
+ editorOptions,
121
121
  }
122
122
  })
123
123
 
124
- export async function addFile(raw: Promise<{ default: string }>, path: string) {
124
+ async function _addFile(raw: Promise<{ default: string }>, path: string) {
125
+ const uri = monaco.Uri.file(path)
125
126
  const code = (await raw).default
126
127
  monaco.languages.typescript.typescriptDefaults.addExtraLib(code, `file:///${path}`)
127
- monaco.editor.createModel(code, 'javascript', monaco.Uri.file(path))
128
+ monaco.editor.createModel(code, 'javascript', uri)
129
+ }
130
+
131
+ const addFileCache = new Map<string, Promise<void>>()
132
+
133
+ export async function addFile(raw: Promise<{ default: string }>, path: string) {
134
+ if (addFileCache.has(path))
135
+ return addFileCache.get(path)
136
+ const promise = _addFile(raw, path)
137
+ addFileCache.set(path, promise)
138
+ return promise
128
139
  }
129
140
 
130
141
  export default setup
package/setup/routes.ts CHANGED
@@ -50,7 +50,7 @@ export default function setupRoutes() {
50
50
  )
51
51
  }
52
52
 
53
- if (__SLIDEV_HAS_SERVER__) {
53
+ if (__SLIDEV_FEATURE_PRINT__) {
54
54
  routes.push(
55
55
  {
56
56
  name: 'print',