diff --git a/packages/client/composables/useNav.ts b/packages/client/composables/useNav.ts index 209860cfbf..87e528f87b 100644 --- a/packages/client/composables/useNav.ts +++ b/packages/client/composables/useNav.ts @@ -86,6 +86,7 @@ export function useNavBase( clicksContext: ComputedRef, queryClicks: Ref = ref(0), isPresenter: Ref, + isPrint: Ref, router?: Router, ): SlidevContextNav { const total = computed(() => slides.value.length) @@ -157,7 +158,9 @@ export function useNavBase( await go( next, lastClicks - ? getSlide(next)?.meta.__clicksContext?.total ?? CLICKS_MAX + ? isPrint.value + ? undefined + : getSlide(next)?.meta.__clicksContext?.total ?? CLICKS_MAX : undefined, ) } @@ -226,6 +229,7 @@ export function useFixedNav( computed(() => clicksContext), ref(CLICKS_MAX), ref(false), + ref(false), ), next: noop, prev: noop, @@ -241,13 +245,18 @@ const useNavState = createSharedComposable((): SlidevContextNavState => { const router = useRouter() const currentRoute = computed(() => router.currentRoute.value) - const isPrintMode = computed(() => currentRoute.value.query.print !== undefined) - const isPrintWithClicks = computed(() => currentRoute.value.query.print === 'clicks') - const isEmbedded = computed(() => currentRoute.value.query.embedded !== undefined) + const query = computed(() => { + // eslint-disable-next-line no-unused-expressions + router.currentRoute.value.query + return new URLSearchParams(location.search) + }) + const isPrintMode = computed(() => query.value.has('print')) + const isPrintWithClicks = computed(() => query.value.get('print') === 'clicks') + const isEmbedded = computed(() => query.value.has('embedded')) const isPlaying = computed(() => currentRoute.value.name === 'play') const isPresenter = computed(() => currentRoute.value.name === 'presenter') const isNotesViewer = computed(() => currentRoute.value.name === 'notes') - const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || currentRoute.value.query.password === configs.remote)) + const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || query.value.get('password') === configs.remote)) const hasPrimarySlide = logicOr(isPlaying, isPresenter) const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1) @@ -335,6 +344,7 @@ export const useNav = createSharedComposable((): SlidevContextNavFull => { state.clicksContext, state.queryClicks, state.isPresenter, + state.isPrintMode, router, ) diff --git a/packages/client/internals/SlideWrapper.vue b/packages/client/internals/SlideWrapper.vue index 565e14fe29..c577d4c114 100644 --- a/packages/client/internals/SlideWrapper.vue +++ b/packages/client/internals/SlideWrapper.vue @@ -55,7 +55,7 @@ const SlideComponent = defineAsyncComponent({ return defineComponent({ setup(_, { attrs }) { onMounted(() => { - props.clicksContext.onMounted() + props.clicksContext?.onMounted?.() }) return () => h(component.default, attrs) }, @@ -70,6 +70,7 @@ const SlideComponent = defineAsyncComponent({ diff --git a/packages/client/internals/SlidesShow.vue b/packages/client/internals/SlidesShow.vue index 3e2e5600ea..c37f5becac 100644 --- a/packages/client/internals/SlidesShow.vue +++ b/packages/client/internals/SlidesShow.vue @@ -5,6 +5,8 @@ import { useNav } from '../composables/useNav' import { getSlideClass } from '../utils' import { useViewTransition } from '../composables/useViewTransition' import { skipTransition } from '../logic/hmr' +import { createFixedClicks } from '../composables/useClicks' +import { CLICKS_MAX } from '../constants' import SlideWrapper from './SlideWrapper.vue' import PresenterMouse from './PresenterMouse.vue' @@ -22,6 +24,8 @@ const { isPresenter, nextRoute, slides, + isPrintMode, + isPrintWithClicks, } = useNav() // preload next route @@ -68,7 +72,7 @@ function onAfterLeave() { > - -../composables/drawings + + diff --git a/packages/slidev/node/commands/export.ts b/packages/slidev/node/commands/export.ts index b01a5e3617..98c61255be 100644 --- a/packages/slidev/node/commands/export.ts +++ b/packages/slidev/node/commands/export.ts @@ -1,5 +1,6 @@ import path from 'node:path' import { Buffer } from 'node:buffer' +import process from 'node:process' import fs from 'fs-extra' import { blue, cyan, dim, green, yellow } from 'kolorist' import { Presets, SingleBar } from 'cli-progress' @@ -183,7 +184,17 @@ export async function exportSlides({ const progress = createSlidevProgress(!perSlide) async function go(no: number | string, clicks?: string) { - const path = `${no}?print${withClicks ? '=clicks' : ''}${clicks ? `&clicks=${clicks}` : ''}${range ? `&range=${range}` : ''}` + const query = new URLSearchParams() + if (withClicks) + query.set('print', 'clicks') + else + query.set('print', 'true') + if (range) + query.set('range', range) + if (clicks) + query.set('clicks', clicks) + + const path = `${no}?${query.toString()}` const url = routerMode === 'hash' ? `http://localhost:${port}${base}#${path}` : `http://localhost:${port}${base}${path}` @@ -193,22 +204,30 @@ export async function exportSlides({ }) await page.waitForLoadState('networkidle') await page.emulateMedia({ colorScheme: dark ? 'dark' : 'light', media: 'screen' }) + const slide = page.locator(`[data-slidev-no="${no}"]`) + await slide.waitFor({ state: 'visible' }) + // Wait for slides to be loaded { - const elements = page.locator('.slidev-slide-loading') + const elements = slide.locator('.slidev-slide-loading') const count = await elements.count() for (let index = 0; index < count; index++) await elements.nth(index).waitFor({ state: 'detached' }) } // Check for "data-waitfor" attribute and wait for given element to be loaded { - const elements = page.locator('[data-waitfor]') + const elements = slide.locator('[data-waitfor]') const count = await elements.count() for (let index = 0; index < count; index++) { const element = elements.nth(index) const attribute = await element.getAttribute('data-waitfor') - if (attribute) - await element.locator(attribute).waitFor() + if (attribute) { + await element.locator(attribute).waitFor({ state: 'visible' }) + .catch((e) => { + console.error(e) + process.exitCode = 1 + }) + } } } // Wait for frames to load @@ -218,18 +237,21 @@ export async function exportSlides({ } // Wait for Mermaid graphs to be rendered { - const container = page.locator('#mermaid-rendering-container') - while (true) { - const element = container.locator('div').first() - if (await element.count() === 0) - break - await element.waitFor({ state: 'detached' }) + const container = slide.locator('#mermaid-rendering-container') + const count = await container.count() + if (count > 0) { + while (true) { + const element = container.locator('div').first() + if (await element.count() === 0) + break + await element.waitFor({ state: 'detached' }) + } + await container.evaluate(node => node.style.display = 'none') } - await container.evaluate(node => node.style.display = 'none') } // Hide Monaco aria container { - const elements = page.locator('.monaco-aria-container') + const elements = slide.locator('.monaco-aria-container') const count = await elements.count() for (let index = 0; index < count; index++) { const element = elements.nth(index)