diff --git a/src/components/markdown-renderer/hooks/use-extract-first-headline.ts b/src/components/markdown-renderer/hooks/use-extract-first-headline.ts index d593a06c0..6bdfb22c9 100644 --- a/src/components/markdown-renderer/hooks/use-extract-first-headline.ts +++ b/src/components/markdown-renderer/hooks/use-extract-first-headline.ts @@ -8,7 +8,7 @@ import React, { useCallback, useEffect, useRef } from 'react' export const useExtractFirstHeadline = ( documentElement: React.RefObject, - content: string, + content: string | undefined, onFirstHeadingChange?: (firstHeading: string | undefined) => void ): void => { const extractInnerText = useCallback((node: ChildNode | null): string => { diff --git a/src/components/markdown-renderer/hooks/use-reveal.ts b/src/components/markdown-renderer/hooks/use-reveal.ts index 540614be1..bed68586c 100644 --- a/src/components/markdown-renderer/hooks/use-reveal.ts +++ b/src/components/markdown-renderer/hooks/use-reveal.ts @@ -4,22 +4,39 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import Reveal from 'reveal.js' import { Logger } from '../../../utils/logger' import { SlideOptions } from '../../common/note-frontmatter/types' const log = new Logger('reveal.js') -export const useReveal = (content: string, slideOptions?: SlideOptions): void => { +export enum REVEAL_STATUS { + NOT_INITIALISED, + INITIALISING, + INITIALISED +} + +export interface SlideState { + indexHorizontal: number + indexVertical: number +} + +const initialSlideState: SlideState = { + indexHorizontal: 0, + indexVertical: 0 +} + +export const useReveal = (content: string, slideOptions?: SlideOptions): REVEAL_STATUS => { const [deck, setDeck] = useState() - const [isInitialized, setIsInitialized] = useState(false) + const [revealStatus, setRevealStatus] = useState(REVEAL_STATUS.NOT_INITIALISED) + const currentSlideState = useRef(initialSlideState) useEffect(() => { - if (isInitialized) { + if (revealStatus !== REVEAL_STATUS.NOT_INITIALISED) { return } - setIsInitialized(true) + setRevealStatus(REVEAL_STATUS.INITIALISING) log.debug('Initialize with slide options', slideOptions) const reveal = new Reveal({}) reveal @@ -27,27 +44,43 @@ export const useReveal = (content: string, slideOptions?: SlideOptions): void => .then(() => { reveal.layout() reveal.slide(0, 0, 0) + reveal.addEventListener('slidechanged', (event) => { + currentSlideState.current = { + indexHorizontal: event.indexh, + indexVertical: event.indexv ?? 0 + } as SlideState + }) + setDeck(reveal) + setRevealStatus(REVEAL_STATUS.INITIALISED) log.debug('Initialisation finished') }) .catch((error: Error) => { log.error('Error while initializing reveal.js', error) }) - }, [isInitialized, slideOptions]) + }, [revealStatus, slideOptions]) useEffect(() => { - if (!deck) { + if (!deck || revealStatus !== REVEAL_STATUS.INITIALISED) { return } log.debug('Sync deck') - deck.layout() - }, [content, deck]) + deck.sync() + deck.slide(currentSlideState.current.indexHorizontal, currentSlideState.current.indexVertical) + }, [content, deck, revealStatus]) useEffect(() => { - if (!deck || slideOptions === undefined || Object.keys(slideOptions).length === 0) { + if ( + !deck || + slideOptions === undefined || + Object.keys(slideOptions).length === 0 || + revealStatus !== REVEAL_STATUS.INITIALISED + ) { return } log.debug('Apply config', slideOptions) deck.configure(slideOptions) - }, [deck, slideOptions]) + }, [deck, revealStatus, slideOptions]) + + return revealStatus } diff --git a/src/components/markdown-renderer/loading-slide.tsx b/src/components/markdown-renderer/loading-slide.tsx new file mode 100644 index 000000000..91b5b0961 --- /dev/null +++ b/src/components/markdown-renderer/loading-slide.tsx @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React from 'react' +import { Trans, useTranslation } from 'react-i18next' + +/** + * Shows a static text placeholder while reveal.js is loading. + */ +export const LoadingSlide: React.FC = () => { + useTranslation() + return ( +
+

+ +

+
+ ) +} diff --git a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx index c6bfc7f3e..97512cb06 100644 --- a/src/components/markdown-renderer/slideshow-markdown-renderer.tsx +++ b/src/components/markdown-renderer/slideshow-markdown-renderer.tsx @@ -12,7 +12,7 @@ import { useExtractFirstHeadline } from './hooks/use-extract-first-headline' import { TocAst } from 'markdown-it-toc-done-right' import { useOnRefChange } from './hooks/use-on-ref-change' import { useTrimmedContent } from './hooks/use-trimmed-content' -import { useReveal } from './hooks/use-reveal' +import { REVEAL_STATUS, useReveal } from './hooks/use-reveal' import './slideshow.scss' import { ScrollProps } from '../editor-page/synced-scroll/scroll-props' import { DocumentLengthLimitReachedAlert } from './document-length-limit-reached-alert' @@ -20,6 +20,7 @@ import { BasicMarkdownItConfigurator } from './markdown-it-configurator/basic-ma import { SlideOptions } from '../common/note-frontmatter/types' import { processRevealCommentNodes } from './process-reveal-comment-nodes' import { CommonMarkdownRendererProps } from './common-markdown-renderer-props' +import { LoadingSlide } from './loading-slide' export interface SlideshowMarkdownRendererProps extends CommonMarkdownRendererProps { slideOptions: SlideOptions @@ -59,17 +60,26 @@ export const SlideshowMarkdownRenderer: React.FC (revealStatus === REVEAL_STATUS.INITIALISED ? markdownReactDom : ), + [markdownReactDom, revealStatus] + ) return (
- {markdownReactDom} + {slideShowDOM}
diff --git a/src/external-types/reveal.js/index.d.ts b/src/external-types/reveal.js/index.d.ts index 43fe56318..fb9ccbcff 100644 --- a/src/external-types/reveal.js/index.d.ts +++ b/src/external-types/reveal.js/index.d.ts @@ -184,9 +184,9 @@ declare module 'reveal.js' { public getPlugins(): { [name: string]: Plugin } // States - // public addEventListener(type: string, listener: (event: any) => void, useCapture?: boolean): void - - // public removeEventListener(type: string, listener: (event: any) => void, useCapture?: boolean): void + // Added only the events we need + public addEventListener(type: 'slidechanged', listener: (event: SlideEvent) => void, useCapture?: boolean): void + public removeEventListener(type: 'slidechanged', listener: (event: SlideEvent) => void, useCapture?: boolean): void // State Checks public isFirstSlide(): boolean