diff --git a/packages/client/composables/useNav.ts b/packages/client/composables/useNav.ts index 0460069c65..86e2de5da3 100644 --- a/packages/client/composables/useNav.ts +++ b/packages/client/composables/useNav.ts @@ -55,7 +55,7 @@ export interface SlidevContextNav { /** Go to previous slide */ prevSlide: (lastClicks?: boolean) => Promise /** Go to slide */ - go: (page: number | string, clicks?: number) => Promise + go: (page: number | string, clicks?: number, force?: boolean) => Promise /** Go to the first slide */ goFirst: () => Promise /** Go to the last slide */ @@ -181,14 +181,14 @@ export function useNavBase( return go(total.value) } - async function go(page: number | string, clicks: number = 0) { + async function go(page: number | string, clicks: number = 0, force = false) { skipTransition.value = false const pageChanged = currentSlideNo.value !== page const clicksChanged = clicks !== queryClicks.value const meta = getSlide(page)?.meta const clicksStart = meta?.slide?.frontmatter.clicksStart ?? 0 clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX) - if (pageChanged || clicksChanged) { + if (force || pageChanged || clicksChanged) { await router?.push({ path: getSlidePath(page, isPresenter.value), query: { @@ -379,9 +379,16 @@ export const useNav = createSharedComposable((): SlidevContextNavFull => { watch( [nav.total, state.currentRoute], async () => { - if (state.hasPrimarySlide.value && !getSlide(state.currentRoute.value.params.no as string)) { - // The current slide may has been removed. Redirect to the last slide. - await nav.goLast() + const no = state.currentRoute.value.params.no as string + if (state.hasPrimarySlide.value && !getSlide(no)) { + if (no && no !== 'index.html') { + // The current slide may has been removed. Redirect to the last slide. + await nav.go(nav.total.value, 0, true) + } + else { + // Redirect to the first slide + await nav.go(1, 0, true) + } } }, { flush: 'pre', immediate: true }, diff --git a/packages/client/constants.ts b/packages/client/constants.ts index 0284c72506..3acfacc867 100644 --- a/packages/client/constants.ts +++ b/packages/client/constants.ts @@ -11,7 +11,6 @@ export const injectionSlideScale = '$$slidev-slide-scale' as unknown as Injectio export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey> export const injectionRoute = '$$slidev-route' as unknown as InjectionKey export const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey> -export const injectionActive = '$$slidev-active' as unknown as InjectionKey> export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey> export const injectionSlideZoom = '$$slidev-slide-zoom' as unknown as InjectionKey> diff --git a/packages/client/env.ts b/packages/client/env.ts index fc9de7674b..59dad897df 100644 --- a/packages/client/env.ts +++ b/packages/client/env.ts @@ -16,3 +16,5 @@ export const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspe export const themeVars = computed(() => { return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v]) }) + +export const slidesTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev') diff --git a/packages/client/internals/Controls.vue b/packages/client/internals/Controls.vue index 8c0e716ece..dc840ae011 100644 --- a/packages/client/internals/Controls.vue +++ b/packages/client/internals/Controls.vue @@ -1,12 +1,19 @@ ../composables/drawings + diff --git a/packages/client/internals/DrawingPreview.vue b/packages/client/internals/DrawingPreview.vue index 36612c7a9e..c21fe541d0 100644 --- a/packages/client/internals/DrawingPreview.vue +++ b/packages/client/internals/DrawingPreview.vue @@ -12,4 +12,4 @@ const { drawingState } = useDrawings() class="w-full h-full absolute top-0 pointer-events-none" v-html="drawingState[page]" /> -../composables/drawings + diff --git a/packages/client/internals/NavControls.vue b/packages/client/internals/NavControls.vue index d531eb6651..72444736fb 100644 --- a/packages/client/internals/NavControls.vue +++ b/packages/client/internals/NavControls.vue @@ -53,10 +53,6 @@ const barStyle = computed(() => props.persist const RecordingControls = shallowRef() if (__SLIDEV_FEATURE_RECORD__) import('./RecordingControls.vue').then(v => RecordingControls.value = v.default) - -const DrawingControls = shallowRef() -if (__SLIDEV_FEATURE_DRAWINGS__) - import('./DrawingControls.vue').then(v => DrawingControls.value = v.default) ../composables/drawings + diff --git a/packages/client/internals/NoteDisplay.vue b/packages/client/internals/NoteDisplay.vue index 51627293ea..0f0baf20f0 100644 --- a/packages/client/internals/NoteDisplay.vue +++ b/packages/client/internals/NoteDisplay.vue @@ -17,7 +17,7 @@ const emit = defineEmits<{ (type: 'markerClick', e: MouseEvent, clicks: number): void }>() -const withClicks = computed(() => props.clicksContext?.current != null && props.noteHtml?.includes('slidev-note-click-mark')) +const withClicks = computed(() => props.clicksContext != null && props.noteHtml?.includes('slidev-note-click-mark')) const noteDisplay = ref(null) const CLASS_FADE = 'slidev-note-fade' diff --git a/packages/client/internals/QuickOverview.vue b/packages/client/internals/QuickOverview.vue index fad425ebaf..b8cc9f2bcc 100644 --- a/packages/client/internals/QuickOverview.vue +++ b/packages/client/internals/QuickOverview.vue @@ -1,10 +1,9 @@ diff --git a/packages/client/internals/SlideContainer.vue b/packages/client/internals/SlideContainer.vue index 9ee65faf6c..435bd17561 100644 --- a/packages/client/internals/SlideContainer.vue +++ b/packages/client/internals/SlideContainer.vue @@ -1,9 +1,10 @@ - diff --git a/packages/client/internals/SlideWrapper.vue b/packages/client/internals/SlideWrapper.vue index 047566cdcb..6b9c5dc8e0 100644 --- a/packages/client/internals/SlideWrapper.vue +++ b/packages/client/internals/SlideWrapper.vue @@ -1,10 +1,11 @@ @@ -85,7 +78,6 @@ const SlideComponent = defineAsyncComponent({ .slidev-page { position: absolute; - width: 100%; - height: 100%; + inset: 0; } diff --git a/packages/client/internals/SlidesShow.vue b/packages/client/internals/SlidesShow.vue index 4cc6fb4819..7e9c5f6850 100644 --- a/packages/client/internals/SlidesShow.vue +++ b/packages/client/internals/SlidesShow.vue @@ -8,7 +8,6 @@ import { createFixedClicks } from '../composables/useClicks' import { activeDragElement } from '../state' import { CLICKS_MAX } from '../constants' import SlideWrapper from './SlideWrapper.vue' -import PresenterMouse from './PresenterMouse.vue' import DragControl from './DragControl.vue' import GlobalTop from '#slidev/global-components/top' @@ -22,11 +21,11 @@ const { currentSlideRoute, currentTransition, getPrimaryClicks, - isPresenter, nextRoute, slides, isPrintMode, isPrintWithClicks, + clicksDirection, } = useNav() // preload next route @@ -64,10 +63,13 @@ function onAfterLeave() { v-bind="skipTransition ? {} : currentTransition" id="slideshow" tag="div" + :class="{ + 'slidev-nav-go-forward': clicksDirection > 0, + 'slidev-nav-go-backward': clicksDirection < 0, + }" @after-leave="onAfterLeave" > - diff --git a/packages/client/pages/notes.vue b/packages/client/pages/notes.vue index 90f83b4f89..7849f1943c 100644 --- a/packages/client/pages/notes.vue +++ b/packages/client/pages/notes.vue @@ -2,7 +2,7 @@ import { useHead } from '@unhead/vue' import { computed, ref, watch } from 'vue' import { useLocalStorage } from '@vueuse/core' -import { configs } from '../env' +import { slidesTitle } from '../env' import { sharedState } from '../state/shared' import { fullscreen } from '../state' @@ -10,10 +10,7 @@ import NoteDisplay from '../internals/NoteDisplay.vue' import IconButton from '../internals/IconButton.vue' import { useNav } from '../composables/useNav' -const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev') -useHead({ - title: `Notes - ${slideTitle}`, -}) +useHead({ title: `Notes - ${slidesTitle}` }) const { slides, total } = useNav() const { isFullscreen, toggle: toggleFullscreen } = fullscreen @@ -39,20 +36,20 @@ function decreaseFontSize() { @@ -227,10 +215,6 @@ onMounted(() => { opacity: 1; } -.section-title { - --uno: px-4 py-2 text-xl; -} - .grid-container { --uno: bg-gray/20; height: 100%; @@ -303,4 +287,4 @@ onMounted(() => { .grid-section.bottom { grid-area: bottom; } -../composables/drawings + diff --git a/packages/client/setup/root.ts b/packages/client/setup/root.ts index 8a443127b8..6903fac351 100644 --- a/packages/client/setup/root.ts +++ b/packages/client/setup/root.ts @@ -1,7 +1,7 @@ import { computed, getCurrentInstance, reactive, ref, shallowRef, watch } from 'vue' import { useHead } from '@unhead/vue' import { useRouter } from 'vue-router' -import { configs } from '../env' +import { configs, slidesTitle } from '../env' import { initSharedState, onPatch, patch } from '../state/shared' import { initDrawingState } from '../state/drawings' import { TRUST_ORIGINS, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext } from '../constants' @@ -51,8 +51,6 @@ export default function setupRoot() { for (const setup of setups) setup() - const title = configs.titleTemplate.replace('%s', configs.title || 'Slidev') - const { clicksContext, currentSlideNo, @@ -62,12 +60,12 @@ export default function setupRoot() { } = useNav() useHead({ - title, + title: slidesTitle, htmlAttrs: configs.htmlAttrs, }) - initSharedState(`${title} - shared`) - initDrawingState(`${title} - drawings`) + initSharedState(`${slidesTitle} - shared`) + initDrawingState(`${slidesTitle} - drawings`) const id = `${location.origin}_${makeId()}` diff --git a/packages/client/styles/index.css b/packages/client/styles/index.css index d3d0ef0b5d..8830dcacd0 100644 --- a/packages/client/styles/index.css +++ b/packages/client/styles/index.css @@ -116,3 +116,7 @@ html { .rough-annotation { transform: scale(calc(1 / var(--slidev-slide-scale))); } + +#twoslash-container { + position: fixed; +} diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 265215a8e1..531c42ca9a 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -113,8 +113,8 @@ export type RenderContext = 'none' | 'slide' | 'overview' | 'presenter' | 'previ export interface SlideRoute { no: number - meta: RouteMeta - component: () => Promise + meta: RouteMeta & Required> + component: () => Promise<{ default: RouteComponent }> } export type LoadedSnippets = Record diff --git a/test/_tutils.ts b/test/_tutils.ts index 1caa8dc9fd..47f8560201 100644 --- a/test/_tutils.ts +++ b/test/_tutils.ts @@ -7,10 +7,6 @@ export function createTransformContext(code: string): MarkdownTransformContext { return { s, id: '1.md', - ignores: [], - isIgnored(index: number) { - return this.ignores.some(([start, end]) => index >= start && index < end) - }, options: { userRoot: path.join(__dirname, './fixtures/'), data: {