mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-07-15 00:52:15 +08:00
feat(lib): enhance notes support (#324)
* feat(lib): enhance notes support TODO: fix keyboard inputs and window order with `present`. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(lint): allow too complex * wip: presenter mode * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat(cli): add presenter view --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -3,6 +3,7 @@ import shutil
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import Parameter, signature
|
from inspect import Parameter, signature
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from textwrap import dedent
|
||||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import rtoml
|
import rtoml
|
||||||
@ -145,6 +146,7 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
|||||||
playback_rate: float = 1.0
|
playback_rate: float = 1.0
|
||||||
reversed_playback_rate: float = 1.0
|
reversed_playback_rate: float = 1.0
|
||||||
notes: str = ""
|
notes: str = ""
|
||||||
|
dedent_notes: bool = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
||||||
@ -188,6 +190,16 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
|||||||
|
|
||||||
return _wrapper_
|
return _wrapper_
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
@classmethod
|
||||||
|
def apply_dedent_notes(
|
||||||
|
cls, base_slide_config: "BaseSlideConfig"
|
||||||
|
) -> "BaseSlideConfig":
|
||||||
|
if base_slide_config.dedent_notes:
|
||||||
|
base_slide_config.notes = dedent(base_slide_config.notes)
|
||||||
|
|
||||||
|
return base_slide_config
|
||||||
|
|
||||||
|
|
||||||
class PreSlideConfig(BaseSlideConfig):
|
class PreSlideConfig(BaseSlideConfig):
|
||||||
"""Slide config to be used prior to rendering."""
|
"""Slide config to be used prior to rendering."""
|
||||||
|
@ -489,7 +489,7 @@ class PowerPoint(Converter):
|
|||||||
def open(self, file: Path) -> None:
|
def open(self, file: Path) -> None:
|
||||||
return open_with_default(file)
|
return open_with_default(file)
|
||||||
|
|
||||||
def convert_to(self, dest: Path) -> None:
|
def convert_to(self, dest: Path) -> None: # noqa: C901
|
||||||
"""Convert this configuration into a PowerPoint presentation, saved to DEST."""
|
"""Convert this configuration into a PowerPoint presentation, saved to DEST."""
|
||||||
prs = pptx.Presentation()
|
prs = pptx.Presentation()
|
||||||
prs.slide_width = self.width * 9525
|
prs.slide_width = self.width * 9525
|
||||||
@ -557,6 +557,9 @@ class PowerPoint(Converter):
|
|||||||
poster_frame_image=poster_frame_image,
|
poster_frame_image=poster_frame_image,
|
||||||
mime_type=mime_type,
|
mime_type=mime_type,
|
||||||
)
|
)
|
||||||
|
if slide_config.notes != "":
|
||||||
|
slide.notes_slide.notes_text_frame.text = slide_config.notes
|
||||||
|
|
||||||
if self.auto_play_media:
|
if self.auto_play_media:
|
||||||
auto_play_media(movie, loop=slide_config.loop)
|
auto_play_media(movie, loop=slide_config.loop)
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QUrl, Signal, Slot
|
from PySide6.QtCore import Qt, QTimer, QUrl, Signal, Slot
|
||||||
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
||||||
from PySide6.QtMultimedia import QMediaPlayer
|
from PySide6.QtMultimedia import QMediaPlayer
|
||||||
from PySide6.QtMultimediaWidgets import QVideoWidget
|
from PySide6.QtMultimediaWidgets import QVideoWidget
|
||||||
from PySide6.QtWidgets import QDialog, QGridLayout, QLabel, QMainWindow, QVBoxLayout
|
from PySide6.QtWidgets import (
|
||||||
|
QHBoxLayout,
|
||||||
|
QLabel,
|
||||||
|
QMainWindow,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
from ..config import Config, PresentationConfig, SlideConfig
|
from ..config import Config, PresentationConfig, SlideConfig
|
||||||
from ..logger import logger
|
from ..logger import logger
|
||||||
@ -14,33 +21,145 @@ from ..resources import * # noqa: F403
|
|||||||
WINDOW_NAME = "Manim Slides"
|
WINDOW_NAME = "Manim Slides"
|
||||||
|
|
||||||
|
|
||||||
class Info(QDialog): # type: ignore[misc]
|
class Info(QWidget): # type: ignore[misc]
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
key_press_event: Signal = Signal(QKeyEvent)
|
||||||
super().__init__(*args, **kwargs)
|
close_event: Signal = Signal(QCloseEvent)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
full_screen: bool,
|
||||||
|
aspect_ratio_mode: Qt.AspectRatioMode,
|
||||||
|
screen: Optional[QScreen],
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
if screen:
|
||||||
|
self.setScreen(screen)
|
||||||
|
self.move(screen.geometry().topLeft())
|
||||||
|
|
||||||
|
if full_screen:
|
||||||
|
self.setWindowState(Qt.WindowFullScreen)
|
||||||
|
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
|
||||||
|
# Current slide view
|
||||||
|
|
||||||
|
left_layout = QVBoxLayout()
|
||||||
|
left_layout.addWidget(
|
||||||
|
QLabel("Current slide"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter,
|
||||||
|
)
|
||||||
|
main_video_widget = QVideoWidget()
|
||||||
|
main_video_widget.setAspectRatioMode(aspect_ratio_mode)
|
||||||
|
main_video_widget.setFixedSize(720, 480)
|
||||||
|
self.video_sink = main_video_widget.videoSink()
|
||||||
|
left_layout.addWidget(main_video_widget)
|
||||||
|
|
||||||
|
# Current slide informations
|
||||||
|
|
||||||
main_layout = QVBoxLayout()
|
|
||||||
labels_layout = QGridLayout()
|
|
||||||
notes_layout = QVBoxLayout()
|
|
||||||
self.scene_label = QLabel()
|
self.scene_label = QLabel()
|
||||||
self.slide_label = QLabel()
|
self.slide_label = QLabel()
|
||||||
self.slide_notes = QLabel("")
|
self.start_time = datetime.now()
|
||||||
|
self.time_label = QLabel()
|
||||||
|
self.elapsed_label = QLabel("00h00m00s")
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.start(1000) # every second
|
||||||
|
self.timer.timeout.connect(self.update_time)
|
||||||
|
|
||||||
|
bottom_left_layout = QHBoxLayout()
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
QLabel("Scene:"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
self.scene_label,
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
QLabel("Slide:"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
self.slide_label,
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
QLabel("Time:"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
self.time_label,
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
QLabel("Elapsed:"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight,
|
||||||
|
)
|
||||||
|
bottom_left_layout.addWidget(
|
||||||
|
self.elapsed_label,
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
|
||||||
|
)
|
||||||
|
left_layout.addLayout(bottom_left_layout)
|
||||||
|
layout.addLayout(left_layout)
|
||||||
|
|
||||||
|
layout.addSpacing(20)
|
||||||
|
|
||||||
|
# Next slide preview
|
||||||
|
|
||||||
|
right_layout = QVBoxLayout()
|
||||||
|
right_layout.addWidget(
|
||||||
|
QLabel("Next slide"),
|
||||||
|
alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter,
|
||||||
|
)
|
||||||
|
next_video_widget = QVideoWidget()
|
||||||
|
next_video_widget.setAspectRatioMode(aspect_ratio_mode)
|
||||||
|
next_video_widget.setFixedSize(360, 240)
|
||||||
|
self.next_media_player = QMediaPlayer()
|
||||||
|
self.next_media_player.setVideoOutput(next_video_widget)
|
||||||
|
self.next_media_player.setLoops(-1)
|
||||||
|
|
||||||
|
right_layout.addWidget(next_video_widget)
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
self.slide_notes = QLabel()
|
||||||
self.slide_notes.setWordWrap(True)
|
self.slide_notes.setWordWrap(True)
|
||||||
|
self.slide_notes.setTextFormat(Qt.TextFormat.MarkdownText)
|
||||||
|
self.slide_notes.setFixedWidth(360)
|
||||||
|
right_layout.addWidget(
|
||||||
|
self.slide_notes,
|
||||||
|
alignment=Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
|
||||||
|
)
|
||||||
|
layout.addLayout(right_layout)
|
||||||
|
|
||||||
labels_layout.addWidget(QLabel("Scene:"), 1, 1)
|
widget = QWidget()
|
||||||
labels_layout.addWidget(QLabel("Slide:"), 2, 1)
|
|
||||||
labels_layout.addWidget(self.scene_label, 1, 2)
|
|
||||||
labels_layout.addWidget(self.slide_label, 2, 2)
|
|
||||||
|
|
||||||
notes_layout.addWidget(self.slide_notes)
|
widget.setLayout(layout)
|
||||||
|
|
||||||
main_layout.addLayout(labels_layout)
|
main_layout = QVBoxLayout()
|
||||||
main_layout.addLayout(notes_layout)
|
main_layout.addWidget(widget, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
self.setLayout(main_layout)
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
if parent := self.parent():
|
@Slot()
|
||||||
self.closeEvent = parent.closeEvent
|
def update_time(self) -> None:
|
||||||
self.keyPressEvent = parent.keyPressEvent
|
now = datetime.now()
|
||||||
|
seconds = (now - self.start_time).total_seconds()
|
||||||
|
hours, seconds = divmod(seconds, 3600)
|
||||||
|
minutes, seconds = divmod(seconds, 60)
|
||||||
|
self.time_label.setText(now.strftime("%Y/%m/%d %H:%M:%S"))
|
||||||
|
self.elapsed_label.setText(
|
||||||
|
f"{int(hours):02d}h{int(minutes):02d}m{int(seconds):02d}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||||
|
self.close_event.emit(event)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||||
|
self.key_press_event.emit(event)
|
||||||
|
|
||||||
|
|
||||||
class Player(QMainWindow): # type: ignore[misc]
|
class Player(QMainWindow): # type: ignore[misc]
|
||||||
@ -107,6 +226,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
self.setWindowIcon(self.icon)
|
self.setWindowIcon(self.icon)
|
||||||
|
|
||||||
self.video_widget = QVideoWidget()
|
self.video_widget = QVideoWidget()
|
||||||
|
self.video_sink = self.video_widget.videoSink()
|
||||||
self.video_widget.setAspectRatioMode(aspect_ratio_mode)
|
self.video_widget.setAspectRatioMode(aspect_ratio_mode)
|
||||||
self.setCentralWidget(self.video_widget)
|
self.setCentralWidget(self.video_widget)
|
||||||
|
|
||||||
@ -117,7 +237,14 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
self.presentation_changed.connect(self.presentation_changed_callback)
|
self.presentation_changed.connect(self.presentation_changed_callback)
|
||||||
self.slide_changed.connect(self.slide_changed_callback)
|
self.slide_changed.connect(self.slide_changed_callback)
|
||||||
|
|
||||||
self.info = Info(parent=self)
|
self.info = Info(
|
||||||
|
full_screen=full_screen, aspect_ratio_mode=aspect_ratio_mode, screen=screen
|
||||||
|
)
|
||||||
|
self.info.close_event.connect(self.closeEvent)
|
||||||
|
self.info.key_press_event.connect(self.keyPressEvent)
|
||||||
|
self.video_sink.videoFrameChanged.connect(
|
||||||
|
lambda frame: self.info.video_sink.setVideoFrame(frame)
|
||||||
|
)
|
||||||
self.hide_info_window = hide_info_window
|
self.hide_info_window = hide_info_window
|
||||||
|
|
||||||
# Connecting key callbacks
|
# Connecting key callbacks
|
||||||
@ -228,6 +355,28 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
def current_file(self, file: Path) -> None:
|
def current_file(self, file: Path) -> None:
|
||||||
self.__current_file = file
|
self.__current_file = file
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_slide_config(self) -> Optional[SlideConfig]:
|
||||||
|
if self.playing_reversed_slide:
|
||||||
|
return self.current_slide_config
|
||||||
|
elif self.current_slide_index < self.current_slides_count - 1:
|
||||||
|
return self.presentation_configs[self.current_presentation_index].slides[
|
||||||
|
self.current_slide_index + 1
|
||||||
|
]
|
||||||
|
elif self.current_presentation_index < self.presentations_count - 1:
|
||||||
|
return self.presentation_configs[
|
||||||
|
self.current_presentation_index + 1
|
||||||
|
].slides[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_file(self) -> Optional[Path]:
|
||||||
|
if slide_config := self.next_slide_config:
|
||||||
|
return slide_config.file # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def playing_reversed_slide(self) -> bool:
|
def playing_reversed_slide(self) -> bool:
|
||||||
return self.__playing_reversed_slide
|
return self.__playing_reversed_slide
|
||||||
@ -286,6 +435,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
def load_next_slide(self) -> None:
|
def load_next_slide(self) -> None:
|
||||||
if self.playing_reversed_slide:
|
if self.playing_reversed_slide:
|
||||||
self.playing_reversed_slide = False
|
self.playing_reversed_slide = False
|
||||||
|
self.preview_next_slide() # Slide number did not change, but next did
|
||||||
elif self.current_slide_index < self.current_slides_count - 1:
|
elif self.current_slide_index < self.current_slides_count - 1:
|
||||||
self.current_slide_index += 1
|
self.current_slide_index += 1
|
||||||
elif self.current_presentation_index < self.presentations_count - 1:
|
elif self.current_presentation_index < self.presentations_count - 1:
|
||||||
@ -321,6 +471,13 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
count = self.current_slides_count
|
count = self.current_slides_count
|
||||||
self.info.slide_label.setText(f"{index+1:4d}/{count:4<d}")
|
self.info.slide_label.setText(f"{index+1:4d}/{count:4<d}")
|
||||||
self.info.slide_notes.setText(self.current_slide_config.notes)
|
self.info.slide_notes.setText(self.current_slide_config.notes)
|
||||||
|
self.preview_next_slide()
|
||||||
|
|
||||||
|
def preview_next_slide(self) -> None:
|
||||||
|
if slide_config := self.next_slide_config:
|
||||||
|
url = QUrl.fromLocalFile(slide_config.file)
|
||||||
|
self.info.next_media_player.setSource(url)
|
||||||
|
self.info.next_media_player.play()
|
||||||
|
|
||||||
def show(self) -> None:
|
def show(self) -> None:
|
||||||
super().show()
|
super().show()
|
||||||
@ -331,6 +488,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
@Slot()
|
@Slot()
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
logger.info("Closing gracefully...")
|
logger.info("Closing gracefully...")
|
||||||
|
self.info.close()
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
@ -353,6 +511,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
@Slot()
|
@Slot()
|
||||||
def reverse(self) -> None:
|
def reverse(self) -> None:
|
||||||
self.load_reversed_slide()
|
self.load_reversed_slide()
|
||||||
|
self.preview_next_slide()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def replay(self) -> None:
|
def replay(self) -> None:
|
||||||
@ -381,9 +540,11 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
else:
|
else:
|
||||||
self.setCursor(Qt.BlankCursor)
|
self.setCursor(Qt.BlankCursor)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||||
key = event.key()
|
key = event.key()
|
||||||
self.dispatch(key)
|
self.dispatch(key)
|
||||||
|
@ -43,7 +43,7 @@ def render(ce: bool, gl: bool, args: Tuple[str, ...]) -> None:
|
|||||||
"""
|
"""
|
||||||
Render SCENE(s) from the input FILE, using the specified renderer.
|
Render SCENE(s) from the input FILE, using the specified renderer.
|
||||||
|
|
||||||
Use 'manim-slides render --help' to see help information for
|
Use ``manim-slides render --help`` to see help information for
|
||||||
a the specified renderer.
|
a the specified renderer.
|
||||||
"""
|
"""
|
||||||
if ce and gl:
|
if ce and gl:
|
||||||
|
@ -289,10 +289,14 @@ class BaseSlide:
|
|||||||
|
|
||||||
Note that this is only supported by ``manim-slides present``.
|
Note that this is only supported by ``manim-slides present``.
|
||||||
:param notes:
|
:param notes:
|
||||||
Presenter notes, in HTML format.
|
Presenter notes, in Markdown format.
|
||||||
|
|
||||||
|
Note that PowerPoint does not support Markdown.
|
||||||
|
|
||||||
Note that this is only supported by ``manim-slides present``
|
Note that this is only supported by ``manim-slides present``
|
||||||
and ``manim-slides convert --to=html``.
|
and ``manim-slides convert --to=html/pptx``.
|
||||||
|
:param dedent_notes:
|
||||||
|
If set, apply :func:`textwrap.dedent` to notes.
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
Keyword arguments to be passed to
|
Keyword arguments to be passed to
|
||||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||||
{%- endif -%}>
|
{%- endif -%}>
|
||||||
{% if slide_config.notes != "" -%}
|
{% if slide_config.notes != "" -%}
|
||||||
<aside class="notes">{{ slide_config.notes }}</aside>
|
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</section>
|
</section>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
@ -54,6 +54,7 @@
|
|||||||
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
||||||
|
|
||||||
{% if has_notes -%}
|
{% if has_notes -%}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
@ -61,7 +62,7 @@
|
|||||||
<script>
|
<script>
|
||||||
Reveal.initialize({
|
Reveal.initialize({
|
||||||
{% if has_notes -%}
|
{% if has_notes -%}
|
||||||
plugins: [ RevealNotes ],
|
plugins: [ RevealMarkdown, RevealNotes ],
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
// The "normal" size of the presentation, aspect ratio will
|
// The "normal" size of the presentation, aspect ratio will
|
||||||
// be preserved when the presentation is scaled to fit different
|
// be preserved when the presentation is scaled to fit different
|
||||||
|
Reference in New Issue
Block a user