@slidev/client 0.48.0-beta.9 → 0.48.1
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/App.vue +7 -0
- package/builtin/Arrow.vue +2 -4
- package/builtin/CodeBlockWrapper.vue +33 -28
- package/builtin/KaTexBlockWrapper.vue +1 -1
- package/builtin/Link.vue +3 -1
- package/builtin/Mermaid.vue +4 -3
- package/builtin/Monaco.vue +166 -93
- package/builtin/ShikiMagicMove.vue +103 -0
- package/builtin/SlidevVideo.vue +1 -1
- package/builtin/Toc.vue +1 -1
- package/builtin/TocList.vue +4 -3
- package/builtin/Tweet.vue +12 -9
- package/builtin/VClick.ts +2 -1
- package/composables/useClicks.ts +19 -32
- package/composables/useDarkMode.ts +9 -0
- package/composables/useDrawings.ts +181 -0
- package/composables/useNav.ts +346 -44
- package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
- package/composables/useSwipeControls.ts +43 -0
- package/composables/useTocTree.ts +81 -0
- package/composables/useViewTransition.ts +7 -4
- package/constants.ts +4 -3
- package/context.ts +13 -6
- package/env.ts +7 -16
- package/index.html +1 -0
- package/index.ts +12 -0
- package/internals/ClicksSlider.vue +97 -0
- package/internals/CodeRunner.vue +142 -0
- package/internals/Controls.vue +2 -2
- package/internals/DomElement.vue +18 -0
- package/internals/DrawingControls.vue +14 -15
- package/internals/DrawingLayer.vue +6 -5
- package/internals/DrawingPreview.vue +4 -2
- package/internals/Goto.vue +9 -6
- package/internals/IconButton.vue +3 -2
- package/internals/NavControls.vue +30 -11
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +129 -0
- package/internals/NoteStatic.vue +5 -2
- package/internals/PrintContainer.vue +11 -8
- package/internals/PrintSlide.vue +11 -12
- package/internals/PrintSlideClick.vue +14 -19
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +3 -3
- package/internals/{Editor.vue → SideEditor.vue} +24 -15
- package/internals/SlideContainer.vue +13 -9
- package/internals/SlideLoading.vue +19 -0
- package/internals/SlideWrapper.vue +79 -0
- package/internals/SlidesShow.vue +36 -22
- package/layouts/error.vue +5 -0
- package/layouts/two-cols-header.vue +9 -3
- package/logic/overview.ts +2 -2
- package/logic/route.ts +16 -5
- package/logic/slides.ts +20 -0
- package/logic/transition.ts +50 -0
- package/logic/utils.ts +24 -1
- package/main.ts +3 -15
- package/{setup → modules}/codemirror.ts +1 -3
- package/modules/context.ts +1 -46
- package/modules/mermaid.ts +9 -8
- package/package.json +21 -15
- package/pages/notes.vue +6 -3
- package/pages/overview.vue +139 -51
- package/pages/play.vue +16 -9
- package/pages/presenter/print.vue +10 -5
- package/pages/presenter.vue +122 -104
- package/pages/print.vue +4 -3
- package/routes.ts +8 -54
- package/setup/code-runners.ts +164 -0
- package/setup/main.ts +39 -9
- package/setup/mermaid.ts +5 -6
- package/setup/monaco.ts +114 -51
- package/setup/root.ts +62 -18
- package/setup/shortcuts.ts +15 -12
- package/shim-vue.d.ts +34 -0
- package/shim.d.ts +1 -13
- package/state/index.ts +2 -2
- package/styles/code.css +9 -5
- package/styles/index.css +63 -7
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +11 -8
- package/styles/shiki-twoslash.css +1 -1
- package/styles/vars.css +1 -1
- package/uno.config.ts +10 -1
- package/utils.ts +15 -2
- package/composables/useContext.ts +0 -17
- package/composables/useTweetScript.ts +0 -17
- package/iframes/monaco/index.css +0 -28
- package/iframes/monaco/index.html +0 -7
- package/iframes/monaco/index.ts +0 -260
- package/internals/NoteEditor.vue +0 -92
- package/internals/SlideWrapper.ts +0 -58
- package/logic/drawings.ts +0 -161
- package/logic/nav.ts +0 -278
- package/setup/prettier.ts +0 -43
- /package/{composables → logic}/hmr.ts +0 -0
|
@@ -9,14 +9,14 @@ export interface UseSlideInfo {
|
|
|
9
9
|
update: (data: SlidePatch) => Promise<SlideInfo | void>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function useSlideInfo(
|
|
13
|
-
if (
|
|
12
|
+
export function useSlideInfo(no: number): UseSlideInfo {
|
|
13
|
+
if (no == null) {
|
|
14
14
|
return {
|
|
15
15
|
info: ref() as Ref<SlideInfo | undefined>,
|
|
16
16
|
update: async () => {},
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
const url = `/@slidev/slide/${
|
|
19
|
+
const url = `/@slidev/slide/${no}.json`
|
|
20
20
|
const { data: info, execute } = useFetch(url).json().get()
|
|
21
21
|
|
|
22
22
|
execute()
|
|
@@ -36,12 +36,12 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (__DEV__) {
|
|
39
|
-
import.meta.hot?.on('slidev-
|
|
40
|
-
if (payload.
|
|
39
|
+
import.meta.hot?.on('slidev:update-slide', (payload) => {
|
|
40
|
+
if (payload.no === no)
|
|
41
41
|
info.value = payload.data
|
|
42
42
|
})
|
|
43
|
-
import.meta.hot?.on('slidev
|
|
44
|
-
if (payload.
|
|
43
|
+
import.meta.hot?.on('slidev:update-note', (payload) => {
|
|
44
|
+
if (payload.no === no && info.value.note?.trim() !== payload.note?.trim())
|
|
45
45
|
info.value = { ...info.value, ...payload }
|
|
46
46
|
})
|
|
47
47
|
}
|
|
@@ -52,20 +52,17 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const map: Record<
|
|
55
|
+
const map: Record<number, UseSlideInfo> = {}
|
|
56
56
|
|
|
57
|
-
export function useDynamicSlideInfo(
|
|
58
|
-
function get(
|
|
59
|
-
|
|
60
|
-
if (!map[i])
|
|
61
|
-
map[i] = useSlideInfo(id)
|
|
62
|
-
return map[i]
|
|
57
|
+
export function useDynamicSlideInfo(no: MaybeRef<number>) {
|
|
58
|
+
function get(no: number) {
|
|
59
|
+
return map[no] ??= useSlideInfo(no)
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
return {
|
|
66
|
-
info: computed(() => get(unref(
|
|
63
|
+
info: computed(() => get(unref(no)).info.value),
|
|
67
64
|
update: async (data: SlidePatch, newId?: number) => {
|
|
68
|
-
const info = get(newId ?? unref(
|
|
65
|
+
const info = get(newId ?? unref(no))
|
|
69
66
|
const newData = await info.update(data)
|
|
70
67
|
if (newData)
|
|
71
68
|
info.info.value = newData
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { timestamp, usePointerSwipe } from '@vueuse/core'
|
|
4
|
+
import { useNav } from '../composables/useNav'
|
|
5
|
+
import { useDrawings } from './useDrawings'
|
|
6
|
+
|
|
7
|
+
export function useSwipeControls(root: Ref<HTMLElement | undefined>) {
|
|
8
|
+
const { next, nextSlide, prev, prevSlide } = useNav()
|
|
9
|
+
const { isDrawing } = useDrawings()
|
|
10
|
+
|
|
11
|
+
const swipeBegin = ref(0)
|
|
12
|
+
const { direction, distanceX, distanceY } = usePointerSwipe(root, {
|
|
13
|
+
pointerTypes: ['touch'],
|
|
14
|
+
onSwipeStart() {
|
|
15
|
+
if (isDrawing.value)
|
|
16
|
+
return
|
|
17
|
+
swipeBegin.value = timestamp()
|
|
18
|
+
},
|
|
19
|
+
onSwipeEnd() {
|
|
20
|
+
if (!swipeBegin.value)
|
|
21
|
+
return
|
|
22
|
+
if (isDrawing.value)
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
const x = Math.abs(distanceX.value)
|
|
26
|
+
const y = Math.abs(distanceY.value)
|
|
27
|
+
if (x / window.innerWidth > 0.3 || x > 75) {
|
|
28
|
+
if (direction.value === 'left')
|
|
29
|
+
next()
|
|
30
|
+
|
|
31
|
+
else
|
|
32
|
+
prev()
|
|
33
|
+
}
|
|
34
|
+
else if (y / window.innerHeight > 0.4 || y > 200) {
|
|
35
|
+
if (direction.value === 'down')
|
|
36
|
+
prevSlide()
|
|
37
|
+
|
|
38
|
+
else
|
|
39
|
+
nextSlide()
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { SlideRoute, TocItem } from '@slidev/types'
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { getSlidePath } from '../logic/slides'
|
|
5
|
+
|
|
6
|
+
function addToTree(tree: TocItem[], route: SlideRoute, level = 1) {
|
|
7
|
+
const titleLevel = route.meta?.slide?.level
|
|
8
|
+
if (titleLevel && titleLevel > level && tree.length > 0) {
|
|
9
|
+
addToTree(tree[tree.length - 1].children, route, level + 1)
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
tree.push({
|
|
13
|
+
no: route.no,
|
|
14
|
+
children: [],
|
|
15
|
+
level,
|
|
16
|
+
path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no, false),
|
|
17
|
+
hideInToc: Boolean(route.meta?.slide?.frontmatter?.hideInToc),
|
|
18
|
+
title: route.meta?.slide?.title,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getTreeWithActiveStatuses(
|
|
24
|
+
tree: TocItem[],
|
|
25
|
+
currentRoute?: SlideRoute,
|
|
26
|
+
hasActiveParent = false,
|
|
27
|
+
parent?: TocItem,
|
|
28
|
+
currentSlideNo?: Ref<number>,
|
|
29
|
+
): TocItem[] {
|
|
30
|
+
return tree.map((item: TocItem) => {
|
|
31
|
+
const clone = {
|
|
32
|
+
...item,
|
|
33
|
+
active: item.no === currentSlideNo?.value,
|
|
34
|
+
hasActiveParent,
|
|
35
|
+
}
|
|
36
|
+
if (clone.children.length > 0) {
|
|
37
|
+
clone.children = getTreeWithActiveStatuses(
|
|
38
|
+
clone.children,
|
|
39
|
+
currentRoute,
|
|
40
|
+
clone.active || clone.hasActiveParent,
|
|
41
|
+
clone,
|
|
42
|
+
currentSlideNo,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
if (parent && (clone.active || clone.activeParent))
|
|
46
|
+
parent.activeParent = true
|
|
47
|
+
return clone
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
52
|
+
return tree
|
|
53
|
+
.filter((item: TocItem) => !item.hideInToc)
|
|
54
|
+
.map((item: TocItem) => ({
|
|
55
|
+
...item,
|
|
56
|
+
children: filterTree(item.children, level + 1),
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useTocTree(
|
|
61
|
+
slides: Ref<SlideRoute[]>,
|
|
62
|
+
currentSlideNo: Ref<number>,
|
|
63
|
+
currentSlideRoute: Ref<SlideRoute>,
|
|
64
|
+
): ComputedRef<TocItem[]> {
|
|
65
|
+
const rawTree = computed(() => slides.value
|
|
66
|
+
.filter((route: SlideRoute) => route.meta?.slide?.title)
|
|
67
|
+
.reduce((acc: TocItem[], route: SlideRoute) => {
|
|
68
|
+
addToTree(acc, route)
|
|
69
|
+
return acc
|
|
70
|
+
}, []))
|
|
71
|
+
|
|
72
|
+
const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(
|
|
73
|
+
rawTree.value,
|
|
74
|
+
currentSlideRoute.value,
|
|
75
|
+
undefined,
|
|
76
|
+
undefined,
|
|
77
|
+
currentSlideNo,
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
return computed(() => filterTree(treeWithActiveStatuses.value))
|
|
81
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import { useRouter } from 'vue-router'
|
|
3
|
+
import { getSlide } from '../logic/slides'
|
|
3
4
|
|
|
4
5
|
export function useViewTransition() {
|
|
5
6
|
const router = useRouter()
|
|
@@ -11,13 +12,15 @@ export function useViewTransition() {
|
|
|
11
12
|
const supportViewTransition = typeof document !== 'undefined' && 'startViewTransition' in document
|
|
12
13
|
|
|
13
14
|
router.beforeResolve((to, from) => {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
15
|
+
const fromMeta = getSlide(from.params.no as string)?.meta
|
|
16
|
+
const toMeta = getSlide(to.params.no as string)?.meta
|
|
17
|
+
const fromNo = fromMeta?.slide?.no
|
|
18
|
+
const toNo = toMeta?.slide?.no
|
|
16
19
|
if (
|
|
17
20
|
!(
|
|
18
21
|
fromNo !== undefined && toNo !== undefined && (
|
|
19
|
-
(
|
|
20
|
-
|| (
|
|
22
|
+
(fromMeta?.transition === 'view-transition' && fromNo < toNo)
|
|
23
|
+
|| (toMeta?.transition === 'view-transition' && toNo < fromNo)
|
|
21
24
|
)
|
|
22
25
|
)
|
|
23
26
|
) {
|
package/constants.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey, Ref, UnwrapNestedRefs } from 'vue'
|
|
2
|
-
import type {
|
|
3
|
-
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
2
|
+
import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
|
|
4
3
|
import type { SlidevContext } from './modules/context'
|
|
5
4
|
|
|
6
5
|
// Here we use string literal instead of symbols to make HMR more stable
|
|
@@ -9,7 +8,7 @@ export const injectionClicksContext = '$$slidev-clicks-context' as unknown as In
|
|
|
9
8
|
export const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>
|
|
10
9
|
export const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>
|
|
11
10
|
export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>
|
|
12
|
-
export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<
|
|
11
|
+
export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<SlideRoute>
|
|
13
12
|
export const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey<Ref<RenderContext>>
|
|
14
13
|
export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
|
|
15
14
|
export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
|
|
@@ -22,6 +21,8 @@ export const CLASS_VCLICK_HIDDEN_EXP = 'slidev-vclick-hidden-explicitly'
|
|
|
22
21
|
export const CLASS_VCLICK_CURRENT = 'slidev-vclick-current'
|
|
23
22
|
export const CLASS_VCLICK_PRIOR = 'slidev-vclick-prior'
|
|
24
23
|
|
|
24
|
+
export const CLICKS_MAX = 999999
|
|
25
|
+
|
|
25
26
|
export const TRUST_ORIGINS = [
|
|
26
27
|
'localhost',
|
|
27
28
|
'127.0.0.1',
|
package/context.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ref, toRef } from 'vue'
|
|
2
2
|
import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
|
|
3
|
-
import { useFixedClicks } from './composables/useClicks'
|
|
4
3
|
import {
|
|
5
4
|
FRONTMATTER_FIELDS,
|
|
6
5
|
HEADMATTER_FIELDS,
|
|
@@ -9,23 +8,23 @@ import {
|
|
|
9
8
|
injectionFrontmatter,
|
|
10
9
|
injectionRenderContext,
|
|
11
10
|
injectionRoute,
|
|
11
|
+
injectionSlideScale,
|
|
12
12
|
injectionSlidevContext,
|
|
13
13
|
} from './constants'
|
|
14
14
|
|
|
15
|
-
const clicksContextFallback = shallowRef(useFixedClicks()[1])
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* Get the current slide context, should be called inside the setup function of a component inside slide
|
|
19
17
|
*/
|
|
20
18
|
export function useSlideContext() {
|
|
21
19
|
const $slidev = injectLocal(injectionSlidevContext)!
|
|
22
20
|
const $nav = toRef($slidev, 'nav')
|
|
23
|
-
const $clicksContext = injectLocal(injectionClicksContext
|
|
21
|
+
const $clicksContext = injectLocal(injectionClicksContext)!.value
|
|
24
22
|
const $clicks = toRef($clicksContext, 'current')
|
|
25
23
|
const $page = injectLocal(injectionCurrentPage)!
|
|
26
24
|
const $renderContext = injectLocal(injectionRenderContext)!
|
|
27
25
|
const $frontmatter = injectLocal(injectionFrontmatter, {})
|
|
28
26
|
const $route = injectLocal(injectionRoute, undefined)
|
|
27
|
+
const $scale = injectLocal(injectionSlideScale, ref(1))!
|
|
29
28
|
|
|
30
29
|
return {
|
|
31
30
|
$slidev,
|
|
@@ -36,9 +35,15 @@ export function useSlideContext() {
|
|
|
36
35
|
$route,
|
|
37
36
|
$renderContext,
|
|
38
37
|
$frontmatter,
|
|
38
|
+
$scale,
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export type SlideContext = ReturnType<typeof useSlideContext>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
42
47
|
export function provideFrontmatter(frontmatter: Record<string, any>) {
|
|
43
48
|
provideLocal(injectionFrontmatter, frontmatter)
|
|
44
49
|
|
|
@@ -48,7 +53,7 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
|
|
|
48
53
|
} = useSlideContext()
|
|
49
54
|
|
|
50
55
|
// update frontmatter in router to make HMR work better
|
|
51
|
-
const route = $slidev.nav.
|
|
56
|
+
const route = $slidev.nav.slides.find(i => i.no === $page.value)
|
|
52
57
|
if (route?.meta?.slide?.frontmatter) {
|
|
53
58
|
for (const key of Object.keys(route.meta.slide.frontmatter)) {
|
|
54
59
|
if (!(key in frontmatter))
|
|
@@ -61,6 +66,8 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
|
|
|
61
66
|
/**
|
|
62
67
|
* Convert frontmatter options to props for v-bind
|
|
63
68
|
* It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
64
71
|
*/
|
|
65
72
|
export function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {
|
|
66
73
|
return {
|
package/env.ts
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { UnwrapNestedRefs } from 'vue'
|
|
3
|
-
import { computed } from 'vue'
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
4
2
|
import { objectMap } from '@antfu/utils'
|
|
3
|
+
import configs from '#slidev/configs'
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export { configs }
|
|
6
|
+
|
|
7
|
+
export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
|
|
8
|
+
export const slideWidth = ref(configs.canvasWidth ?? 980)
|
|
9
9
|
|
|
10
|
-
export const configs = _configs as SlidevConfig
|
|
11
|
-
export const slideAspect = configs.aspectRatio ?? (16 / 9)
|
|
12
|
-
export const slideWidth = configs.canvasWidth ?? 980
|
|
13
10
|
// To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
|
|
14
11
|
// Doing this, we will prevent on print, to create an additional empty white page after each page.
|
|
15
|
-
export const slideHeight = Math.ceil(slideWidth / slideAspect)
|
|
12
|
+
export const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspect.value))
|
|
16
13
|
|
|
17
14
|
export const themeVars = computed(() => {
|
|
18
15
|
return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
|
|
19
16
|
})
|
|
20
|
-
|
|
21
|
-
declare module 'vue' {
|
|
22
|
-
interface ComponentCustomProperties {
|
|
23
|
-
$slidev: UnwrapNestedRefs<SlidevContext>
|
|
24
|
-
}
|
|
25
|
-
}
|
package/index.html
CHANGED
package/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is the public APIs that you might use in your app.
|
|
3
|
+
*
|
|
4
|
+
* The other files despite they are accessable, are not meant to be used directly, breaking changes might happen.
|
|
5
|
+
*/
|
|
6
|
+
export { useSlideContext } from './context'
|
|
7
|
+
export { useNav } from './composables/useNav'
|
|
8
|
+
export { useDrawings } from './composables/useDrawings'
|
|
9
|
+
export { useDarkMode } from './composables/useDarkMode'
|
|
10
|
+
|
|
11
|
+
export * from './layoutHelper'
|
|
12
|
+
export * from './env'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ClicksContext } from '@slidev/types'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { CLICKS_MAX } from '../constants'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
clicksContext: ClicksContext
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const total = computed(() => props.clicksContext.total)
|
|
11
|
+
const current = computed({
|
|
12
|
+
get() {
|
|
13
|
+
return props.clicksContext.current > total.value ? -1 : props.clicksContext.current
|
|
14
|
+
},
|
|
15
|
+
set(value: number) {
|
|
16
|
+
// eslint-disable-next-line vue/no-mutating-props
|
|
17
|
+
props.clicksContext.current = value
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const range = computed(() => Array.from({ length: total.value + 1 }, (_, i) => i))
|
|
22
|
+
|
|
23
|
+
function onMousedown() {
|
|
24
|
+
if (current.value < 0 || current.value > total.value)
|
|
25
|
+
current.value = 0
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div
|
|
31
|
+
class="flex gap-0.5 items-center select-none"
|
|
32
|
+
:title="`Clicks in this slide: ${total}`"
|
|
33
|
+
:class="total ? '' : 'op50'"
|
|
34
|
+
>
|
|
35
|
+
<div class="flex gap-1 items-center min-w-16 tabular-nums">
|
|
36
|
+
<carbon:cursor-1 text-sm op50 />
|
|
37
|
+
<template v-if="current >= 0 && current !== CLICKS_MAX">
|
|
38
|
+
<div flex-auto />
|
|
39
|
+
<span text-primary>{{ current }}</span>
|
|
40
|
+
<span op25>/</span>
|
|
41
|
+
</template>
|
|
42
|
+
<span op50>{{ total }}</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div
|
|
45
|
+
relative flex-auto h5 flex="~"
|
|
46
|
+
@dblclick="current = clicksContext.total"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
v-for="i of range" :key="i"
|
|
50
|
+
border="y main" of-hidden relative
|
|
51
|
+
:class="[
|
|
52
|
+
i === 0 ? 'rounded-l border-l' : '',
|
|
53
|
+
i === total ? 'rounded-r border-r' : '',
|
|
54
|
+
]"
|
|
55
|
+
:style="{ width: total > 0 ? `${1 / total * 100}%` : '100%' }"
|
|
56
|
+
>
|
|
57
|
+
<div absolute inset-0 :class="i <= current ? 'bg-primary op20' : ''" />
|
|
58
|
+
<div
|
|
59
|
+
:class="[
|
|
60
|
+
+i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
|
|
61
|
+
i === 0 ? 'rounded-l' : '',
|
|
62
|
+
i === total ? 'rounded-r' : 'border-r-2',
|
|
63
|
+
]"
|
|
64
|
+
w-full h-full text-xs flex items-center justify-center z-1
|
|
65
|
+
>
|
|
66
|
+
{{ i }}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<input
|
|
70
|
+
v-model="current"
|
|
71
|
+
class="range" absolute inset-0
|
|
72
|
+
type="range" :min="0" :max="total" :step="1" z-10 op0
|
|
73
|
+
:style="{ '--thumb-width': `${1 / (total + 1) * 100}%` }"
|
|
74
|
+
@mousedown="onMousedown"
|
|
75
|
+
@focus="event => (event.currentTarget as HTMLElement)?.blur()"
|
|
76
|
+
>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style scoped>
|
|
82
|
+
.range {
|
|
83
|
+
-webkit-appearance: none;
|
|
84
|
+
appearance: none;
|
|
85
|
+
background: transparent;
|
|
86
|
+
}
|
|
87
|
+
.range::-webkit-slider-thumb {
|
|
88
|
+
-webkit-appearance: none;
|
|
89
|
+
height: 100%;
|
|
90
|
+
width: var(--thumb-width, 0.5rem);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.range::-moz-range-thumb {
|
|
94
|
+
height: 100%;
|
|
95
|
+
width: var(--thumb-width, 0.5rem);
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { debounce, toArray } from '@antfu/utils'
|
|
3
|
+
import { useVModel } from '@vueuse/core'
|
|
4
|
+
import type { CodeRunnerOutput } from '@slidev/types'
|
|
5
|
+
import { computed, ref, shallowRef, watch } from 'vue'
|
|
6
|
+
import { useSlideContext } from '../context'
|
|
7
|
+
import setupCodeRunners from '../setup/code-runners'
|
|
8
|
+
import { useNav } from '../composables/useNav'
|
|
9
|
+
import IconButton from './IconButton.vue'
|
|
10
|
+
import DomElement from './DomElement.vue'
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
modelValue: string
|
|
14
|
+
lang: string
|
|
15
|
+
autorun: boolean | 'once'
|
|
16
|
+
height?: string
|
|
17
|
+
highlightOutput: boolean
|
|
18
|
+
runnerOptions?: Record<string, unknown>
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits(['update:modelValue'])
|
|
22
|
+
|
|
23
|
+
const { isPrintMode } = useNav()
|
|
24
|
+
|
|
25
|
+
const code = useVModel(props, 'modelValue', emit)
|
|
26
|
+
|
|
27
|
+
const { $renderContext } = useSlideContext()
|
|
28
|
+
const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value))
|
|
29
|
+
|
|
30
|
+
const autorun = isPrintMode.value ? 'once' : props.autorun
|
|
31
|
+
const isRunning = ref(autorun)
|
|
32
|
+
const outputs = shallowRef<CodeRunnerOutput[]>()
|
|
33
|
+
const runCount = ref(0)
|
|
34
|
+
const highlightFn = ref<(code: string, lang: string) => string>()
|
|
35
|
+
|
|
36
|
+
const triggerRun = debounce(200, async () => {
|
|
37
|
+
if (disabled.value)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
const { highlight, run } = await setupCodeRunners()
|
|
41
|
+
highlightFn.value = highlight
|
|
42
|
+
|
|
43
|
+
const setAsRunning = setTimeout(() => {
|
|
44
|
+
isRunning.value = true
|
|
45
|
+
}, 500)
|
|
46
|
+
|
|
47
|
+
outputs.value = toArray(await run(code.value, props.lang, props.runnerOptions ?? {}))
|
|
48
|
+
runCount.value += 1
|
|
49
|
+
isRunning.value = false
|
|
50
|
+
|
|
51
|
+
clearTimeout(setAsRunning)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (autorun === 'once')
|
|
55
|
+
triggerRun()
|
|
56
|
+
else if (autorun)
|
|
57
|
+
watch(code, triggerRun, { immediate: true })
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<div
|
|
62
|
+
class="relative flex flex-col rounded-b border-t border-main"
|
|
63
|
+
:style="{ height: props.height }"
|
|
64
|
+
data-waitfor=".slidev-runner-output"
|
|
65
|
+
>
|
|
66
|
+
<div v-if="disabled" class="text-sm text-center opacity-50">
|
|
67
|
+
Code is disabled in the "{{ $renderContext }}" mode
|
|
68
|
+
</div>
|
|
69
|
+
<div v-else-if="isRunning" class="text-sm text-center opacity-50">
|
|
70
|
+
Running...
|
|
71
|
+
</div>
|
|
72
|
+
<div v-else-if="!outputs?.length" class="text-sm text-center opacity-50">
|
|
73
|
+
Click the play button to run the code
|
|
74
|
+
</div>
|
|
75
|
+
<div v-else :key="`run-${runCount}`" class="slidev-runner-output">
|
|
76
|
+
<template v-for="output, _idx1 of outputs" :key="_idx1">
|
|
77
|
+
<div v-if="'html' in output" v-html="output.html" />
|
|
78
|
+
<div v-else-if="'error' in output" class="text-red-500">
|
|
79
|
+
{{ output.error }}
|
|
80
|
+
</div>
|
|
81
|
+
<DomElement v-else-if="'element' in output" :element="output.element" />
|
|
82
|
+
<div v-else class="output-line">
|
|
83
|
+
<template
|
|
84
|
+
v-for="item, idx2 in toArray(output)"
|
|
85
|
+
:key="idx2"
|
|
86
|
+
>
|
|
87
|
+
<span
|
|
88
|
+
v-if="item.highlightLang && highlightFn"
|
|
89
|
+
class="highlighted"
|
|
90
|
+
v-html="highlightFn(item.text, item.highlightLang)"
|
|
91
|
+
/>
|
|
92
|
+
<span v-else :class="item.class">{{ item.text }}</span>
|
|
93
|
+
<span v-if="idx2 < toArray(output).length - 1" class="separator">,</span>
|
|
94
|
+
</template>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div v-if="code.trim()" class="absolute right-1 top-1 max-h-full flex gap-1">
|
|
100
|
+
<IconButton class="w-8 h-8 max-h-full flex justify-center items-center" title="Run code" @click="triggerRun">
|
|
101
|
+
<carbon:play />
|
|
102
|
+
</IconButton>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<style lang="postcss">
|
|
107
|
+
.slidev-runner-output {
|
|
108
|
+
@apply px-5 py-3 flex-grow text-xs leading-[.8rem] font-$slidev-code-font-family select-text;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.slidev-runner-output .log-type {
|
|
112
|
+
@apply font-bold op-70;
|
|
113
|
+
|
|
114
|
+
&.DBG {
|
|
115
|
+
@apply text-gray-500;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&.LOG {
|
|
119
|
+
@apply text-blue-500;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&.WRN {
|
|
123
|
+
@apply text-orange-500;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&.ERR {
|
|
127
|
+
@apply text-red-500;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.slidev-runner-output .output-line {
|
|
132
|
+
@apply flex my-1 w-full;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.slidev-runner-output .separator {
|
|
136
|
+
@apply op-40 mr-1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.slidev-runner-output .highlighted > pre {
|
|
140
|
+
@apply inline text-wrap !bg-transparent;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
package/internals/Controls.vue
CHANGED
|
@@ -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
|
|
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
|
-
<
|
|
18
|
+
<QuickOverview v-model="showOverview" />
|
|
19
19
|
<Goto />
|
|
20
20
|
<WebCamera v-if="WebCamera" />
|
|
21
21
|
<RecordingDialog v-if="RecordingDialog" v-model="showRecordingDialog" />
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watchEffect } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
element: HTMLElement
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const container = ref<HTMLElement>()
|
|
9
|
+
|
|
10
|
+
watchEffect(() => {
|
|
11
|
+
if (container.value)
|
|
12
|
+
container.value.appendChild(props.element)
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div ref="container" />
|
|
18
|
+
</template>
|