Merge lp:~openlp-dev/openlp/webengine-migrate into lp:openlp

Proposed by Tomas Groth
Status: Superseded
Proposed branch: lp:~openlp-dev/openlp/webengine-migrate
Merge into: lp:openlp
Diff against target: 33116 lines (+15169/-5298)
370 files modified
.bzrignore (+7/-0)
karma.conf.js (+77/-0)
nose2.cfg (+8/-8)
openlp/.version (+1/-1)
openlp/core/api/deploy.py (+1/-1)
openlp/core/api/endpoint/controller.py (+8/-6)
openlp/core/api/endpoint/core.py (+2/-2)
openlp/core/api/endpoint/remote.py (+1/-0)
openlp/core/api/endpoint/service.py (+1/-0)
openlp/core/api/http/server.py (+6/-7)
openlp/core/api/http/wsgiapp.py (+1/-0)
openlp/core/api/tab.py (+4/-3)
openlp/core/api/websockets.py (+1/-0)
openlp/core/app.py (+10/-7)
openlp/core/common/actions.py (+1/-1)
openlp/core/common/applocation.py (+2/-1)
openlp/core/common/db.py (+1/-0)
openlp/core/common/httputils.py (+1/-0)
openlp/core/common/i18n.py (+2/-1)
openlp/core/common/mixins.py (+1/-0)
openlp/core/common/path.py (+1/-0)
openlp/core/common/registry.py (+2/-0)
openlp/core/common/settings.py (+41/-4)
openlp/core/display/html/black.css (+292/-0)
openlp/core/display/html/display.html (+39/-0)
openlp/core/display/html/display.js (+789/-0)
openlp/core/display/html/reveal.css (+1591/-0)
openlp/core/display/html/reveal.js (+5586/-0)
openlp/core/display/html/textFit.js (+237/-0)
openlp/core/display/render.py (+738/-0)
openlp/core/display/renderer.py (+0/-586)
openlp/core/display/screens.py (+283/-181)
openlp/core/display/webengine.py (+93/-0)
openlp/core/display/window.py (+404/-0)
openlp/core/lib/__init__.py (+97/-307)
openlp/core/lib/db.py (+4/-3)
openlp/core/lib/htmlbuilder.py (+9/-7)
openlp/core/lib/imagemanager.py (+6/-5)
openlp/core/lib/mediamanageritem.py (+6/-5)
openlp/core/lib/plugin.py (+1/-0)
openlp/core/lib/pluginmanager.py (+2/-0)
openlp/core/lib/serviceitem.py (+105/-191)
openlp/core/lib/settingstab.py (+4/-4)
openlp/core/lib/theme.py (+9/-7)
openlp/core/lib/ui.py (+1/-0)
openlp/core/loader.py (+1/-1)
openlp/core/projectors/constants.py (+1/-0)
openlp/core/projectors/db.py (+7/-3)
openlp/core/projectors/editform.py (+6/-5)
openlp/core/projectors/manager.py (+6/-5)
openlp/core/projectors/pjlink.py (+6/-5)
openlp/core/projectors/sourceselectform.py (+2/-1)
openlp/core/projectors/tab.py (+5/-4)
openlp/core/projectors/upgrade.py (+2/-1)
openlp/core/resources.py (+1/-1)
openlp/core/server.py (+1/-1)
openlp/core/ui/aboutform.py (+1/-0)
openlp/core/ui/advancedtab.py (+5/-4)
openlp/core/ui/exceptiondialog.py (+4/-4)
openlp/core/ui/exceptionform.py (+7/-46)
openlp/core/ui/filerenamedialog.py (+3/-3)
openlp/core/ui/filerenameform.py (+1/-1)
openlp/core/ui/firsttimeform.py (+42/-39)
openlp/core/ui/firsttimelanguagedialog.py (+3/-3)
openlp/core/ui/firsttimelanguageform.py (+2/-1)
openlp/core/ui/firsttimewizard.py (+29/-32)
openlp/core/ui/formattingtagdialog.py (+4/-4)
openlp/core/ui/formattingtagform.py (+1/-1)
openlp/core/ui/generaltab.py (+8/-144)
openlp/core/ui/icons.py (+2/-1)
openlp/core/ui/maindisplay.py (+0/-602)
openlp/core/ui/mainwindow.py (+20/-27)
openlp/core/ui/media/endpoint.py (+1/-0)
openlp/core/ui/media/mediacontroller.py (+5/-4)
openlp/core/ui/media/playertab.py (+4/-4)
openlp/core/ui/media/systemplayer.py (+2/-1)
openlp/core/ui/media/vendor/vlc.py (+1197/-892)
openlp/core/ui/media/vlcplayer.py (+2/-1)
openlp/core/ui/media/webkitplayer.py (+0/-312)
openlp/core/ui/plugindialog.py (+3/-3)
openlp/core/ui/pluginform.py (+2/-1)
openlp/core/ui/printservicedialog.py (+4/-4)
openlp/core/ui/printserviceform.py (+3/-2)
openlp/core/ui/screenstab.py (+97/-0)
openlp/core/ui/serviceitemeditdialog.py (+4/-4)
openlp/core/ui/serviceitemeditform.py (+3/-3)
openlp/core/ui/servicemanager.py (+20/-16)
openlp/core/ui/servicenoteform.py (+4/-4)
openlp/core/ui/settingsdialog.py (+3/-3)
openlp/core/ui/settingsform.py (+21/-13)
openlp/core/ui/shortcutlistdialog.py (+3/-3)
openlp/core/ui/shortcutlistform.py (+2/-1)
openlp/core/ui/slidecontroller.py (+165/-167)
openlp/core/ui/splashscreen.py (+2/-2)
openlp/core/ui/starttimedialog.py (+3/-3)
openlp/core/ui/starttimeform.py (+1/-1)
openlp/core/ui/style.py (+1/-0)
openlp/core/ui/themeform.py (+14/-8)
openlp/core/ui/themelayoutdialog.py (+3/-3)
openlp/core/ui/themelayoutform.py (+1/-1)
openlp/core/ui/thememanager.py (+10/-10)
openlp/core/ui/themestab.py (+7/-7)
openlp/core/ui/themewizard.py (+4/-4)
openlp/core/version.py (+54/-2)
openlp/core/widgets/docks.py (+2/-1)
openlp/core/widgets/edits.py (+2/-1)
openlp/core/widgets/layouts.py (+185/-0)
openlp/core/widgets/toolbar.py (+1/-0)
openlp/core/widgets/views.py (+40/-38)
openlp/core/widgets/widgets.py (+267/-1)
openlp/core/widgets/wizard.py (+5/-4)
openlp/plugins/alerts/alertsplugin.py (+3/-2)
openlp/plugins/alerts/endpoint.py (+1/-0)
openlp/plugins/alerts/forms/__init__.py (+1/-1)
openlp/plugins/alerts/forms/alertdialog.py (+4/-4)
openlp/plugins/alerts/forms/alertform.py (+2/-1)
openlp/plugins/alerts/lib/alertsmanager.py (+5/-4)
openlp/plugins/alerts/lib/alertstab.py (+3/-3)
openlp/plugins/bibles/bibleplugin.py (+1/-0)
openlp/plugins/bibles/endpoint.py (+2/-1)
openlp/plugins/bibles/forms/bibleimportform.py (+7/-6)
openlp/plugins/bibles/forms/booknamedialog.py (+3/-3)
openlp/plugins/bibles/forms/booknameform.py (+2/-1)
openlp/plugins/bibles/forms/editbibledialog.py (+5/-5)
openlp/plugins/bibles/forms/editbibleform.py (+3/-1)
openlp/plugins/bibles/forms/languagedialog.py (+3/-3)
openlp/plugins/bibles/forms/languageform.py (+2/-1)
openlp/plugins/bibles/lib/biblestab.py (+6/-5)
openlp/plugins/bibles/lib/db.py (+4/-3)
openlp/plugins/bibles/lib/importers/csvbible.py (+1/-0)
openlp/plugins/bibles/lib/importers/http.py (+1/-0)
openlp/plugins/bibles/lib/importers/osis.py (+1/-0)
openlp/plugins/bibles/lib/importers/sword.py (+1/-0)
openlp/plugins/bibles/lib/importers/wordproject.py (+2/-1)
openlp/plugins/bibles/lib/importers/zefania.py (+1/-0)
openlp/plugins/bibles/lib/manager.py (+2/-0)
openlp/plugins/bibles/lib/mediaitem.py (+7/-7)
openlp/plugins/bibles/lib/upgrade.py (+1/-0)
openlp/plugins/custom/customplugin.py (+3/-2)
openlp/plugins/custom/endpoint.py (+2/-1)
openlp/plugins/custom/forms/editcustomdialog.py (+4/-4)
openlp/plugins/custom/forms/editcustomform.py (+1/-1)
openlp/plugins/custom/forms/editcustomslidedialog.py (+3/-3)
openlp/plugins/custom/forms/editcustomslideform.py (+2/-1)
openlp/plugins/custom/lib/customtab.py (+3/-3)
openlp/plugins/custom/lib/customxmlhandler.py (+1/-0)
openlp/plugins/custom/lib/mediaitem.py (+5/-4)
openlp/plugins/images/endpoint.py (+2/-1)
openlp/plugins/images/forms/__init__.py (+1/-1)
openlp/plugins/images/forms/addgroupdialog.py (+3/-3)
openlp/plugins/images/forms/addgroupform.py (+1/-1)
openlp/plugins/images/forms/choosegroupdialog.py (+3/-3)
openlp/plugins/images/forms/choosegroupform.py (+1/-1)
openlp/plugins/images/imageplugin.py (+3/-2)
openlp/plugins/images/lib/imagetab.py (+3/-3)
openlp/plugins/images/lib/mediaitem.py (+5/-5)
openlp/plugins/images/lib/upgrade.py (+1/-0)
openlp/plugins/media/endpoint.py (+2/-1)
openlp/plugins/media/forms/mediaclipselectordialog.py (+3/-3)
openlp/plugins/media/forms/mediaclipselectorform.py (+3/-2)
openlp/plugins/media/lib/mediaitem.py (+4/-3)
openlp/plugins/media/lib/mediatab.py (+3/-3)
openlp/plugins/media/mediaplugin.py (+3/-0)
openlp/plugins/presentations/endpoint.py (+2/-1)
openlp/plugins/presentations/lib/impresscontroller.py (+4/-3)
openlp/plugins/presentations/lib/mediaitem.py (+5/-5)
openlp/plugins/presentations/lib/messagelistener.py (+1/-0)
openlp/plugins/presentations/lib/pdfcontroller.py (+3/-2)
openlp/plugins/presentations/lib/powerpointcontroller.py (+21/-10)
openlp/plugins/presentations/lib/presentationcontroller.py (+1/-0)
openlp/plugins/presentations/lib/presentationtab.py (+3/-3)
openlp/plugins/presentations/presentationplugin.py (+2/-1)
openlp/plugins/songs/endpoint.py (+2/-1)
openlp/plugins/songs/forms/__init__.py (+1/-1)
openlp/plugins/songs/forms/authorsdialog.py (+3/-3)
openlp/plugins/songs/forms/authorsform.py (+1/-1)
openlp/plugins/songs/forms/duplicatesongremovalform.py (+2/-1)
openlp/plugins/songs/forms/editsongdialog.py (+4/-4)
openlp/plugins/songs/forms/editsongform.py (+6/-5)
openlp/plugins/songs/forms/editversedialog.py (+3/-3)
openlp/plugins/songs/forms/editverseform.py (+2/-1)
openlp/plugins/songs/forms/mediafilesdialog.py (+3/-3)
openlp/plugins/songs/forms/mediafilesform.py (+2/-1)
openlp/plugins/songs/forms/songbookdialog.py (+3/-3)
openlp/plugins/songs/forms/songbookform.py (+1/-1)
openlp/plugins/songs/forms/songexportform.py (+4/-3)
openlp/plugins/songs/forms/songimportform.py (+4/-3)
openlp/plugins/songs/forms/songmaintenancedialog.py (+3/-3)
openlp/plugins/songs/forms/songmaintenanceform.py (+5/-3)
openlp/plugins/songs/forms/songreviewwidget.py (+4/-4)
openlp/plugins/songs/forms/songselectform.py (+1/-0)
openlp/plugins/songs/forms/topicsdialog.py (+3/-3)
openlp/plugins/songs/forms/topicsform.py (+1/-1)
openlp/plugins/songs/lib/__init__.py (+2/-2)
openlp/plugins/songs/lib/db.py (+2/-2)
openlp/plugins/songs/lib/importer.py (+2/-0)
openlp/plugins/songs/lib/importers/cclifile.py (+2/-0)
openlp/plugins/songs/lib/importers/chordpro.py (+1/-0)
openlp/plugins/songs/lib/importers/dreambeam.py (+1/-0)
openlp/plugins/songs/lib/importers/easyslides.py (+1/-0)
openlp/plugins/songs/lib/importers/easyworship.py (+3/-2)
openlp/plugins/songs/lib/importers/foilpresenter.py (+2/-1)
openlp/plugins/songs/lib/importers/lyrix.py (+1/-0)
openlp/plugins/songs/lib/importers/mediashout.py (+1/-0)
openlp/plugins/songs/lib/importers/openlp.py (+4/-2)
openlp/plugins/songs/lib/importers/openlyrics.py (+1/-0)
openlp/plugins/songs/lib/importers/openoffice.py (+2/-0)
openlp/plugins/songs/lib/importers/opensong.py (+1/-0)
openlp/plugins/songs/lib/importers/opspro.py (+1/-0)
openlp/plugins/songs/lib/importers/powersong.py (+1/-0)
openlp/plugins/songs/lib/importers/presentationmanager.py (+1/-1)
openlp/plugins/songs/lib/importers/propresenter.py (+1/-0)
openlp/plugins/songs/lib/importers/songbeamer.py (+2/-1)
openlp/plugins/songs/lib/importers/songimport.py (+3/-2)
openlp/plugins/songs/lib/importers/songshowplus.py (+1/-0)
openlp/plugins/songs/lib/importers/songsoffellowship.py (+2/-0)
openlp/plugins/songs/lib/importers/sundayplus.py (+2/-2)
openlp/plugins/songs/lib/importers/videopsalm.py (+1/-0)
openlp/plugins/songs/lib/importers/wordsofworship.py (+1/-0)
openlp/plugins/songs/lib/importers/worshipassistant.py (+1/-0)
openlp/plugins/songs/lib/importers/worshipcenterpro.py (+1/-0)
openlp/plugins/songs/lib/importers/zionworx.py (+1/-0)
openlp/plugins/songs/lib/mediaitem.py (+5/-4)
openlp/plugins/songs/lib/openlyricsexport.py (+1/-0)
openlp/plugins/songs/lib/openlyricsxml.py (+1/-0)
openlp/plugins/songs/lib/songselect.py (+1/-0)
openlp/plugins/songs/lib/songstab.py (+3/-3)
openlp/plugins/songs/lib/upgrade.py (+3/-2)
openlp/plugins/songs/reporting.py (+1/-0)
openlp/plugins/songs/songsplugin.py (+5/-5)
openlp/plugins/songusage/forms/songusagedeletedialog.py (+3/-3)
openlp/plugins/songusage/forms/songusagedeleteform.py (+1/-1)
openlp/plugins/songusage/forms/songusagedetaildialog.py (+3/-3)
openlp/plugins/songusage/forms/songusagedetailform.py (+3/-1)
openlp/plugins/songusage/lib/upgrade.py (+2/-1)
openlp/plugins/songusage/songusageplugin.py (+3/-2)
package.json (+25/-0)
run_openlp.py (+3/-1)
scripts/appveyor-webhook.py (+5/-4)
scripts/appveyor.yml (+4/-10)
scripts/check_dependencies.py (+1/-2)
scripts/clean_up_resources.py (+1/-0)
scripts/jenkins_script.py (+2/-1)
scripts/lp-merge.py (+5/-4)
scripts/mp_update.py (+3/-1)
scripts/reveal-js.patch (+25/-0)
scripts/translation_utils.py (+3/-3)
scripts/websocket_client.py (+2/-1)
setup.cfg (+1/-2)
setup.py (+2/-1)
tests/functional/openlp_core/api/endpoint/test_controller.py (+35/-13)
tests/functional/openlp_core/api/endpoint/test_remote.py (+1/-1)
tests/functional/openlp_core/api/http/test_init.py (+1/-2)
tests/functional/openlp_core/api/http/test_wsgiapp.py (+1/-0)
tests/functional/openlp_core/api/test_deploy.py (+7/-4)
tests/functional/openlp_core/api/test_tab.py (+1/-0)
tests/functional/openlp_core/api/test_websockets.py (+1/-0)
tests/functional/openlp_core/common/test_actions.py (+123/-128)
tests/functional/openlp_core/common/test_applocation.py (+1/-0)
tests/functional/openlp_core/common/test_common.py (+2/-2)
tests/functional/openlp_core/common/test_db.py (+1/-1)
tests/functional/openlp_core/common/test_path.py (+2/-2)
tests/functional/openlp_core/display/test_render.py (+210/-0)
tests/functional/openlp_core/display/test_renderer.py (+0/-208)
tests/functional/openlp_core/display/test_screens.py (+66/-18)
tests/functional/openlp_core/lib/test_db.py (+2/-2)
tests/functional/openlp_core/lib/test_formattingtags.py (+1/-0)
tests/functional/openlp_core/lib/test_htmlbuilder.py (+4/-13)
tests/functional/openlp_core/lib/test_image_manager.py (+2/-1)
tests/functional/openlp_core/lib/test_lib.py (+3/-177)
tests/functional/openlp_core/lib/test_serviceitem.py (+35/-55)
tests/functional/openlp_core/lib/test_ui.py (+4/-4)
tests/functional/openlp_core/test_app.py (+3/-0)
tests/functional/openlp_core/test_server.py (+6/-5)
tests/functional/openlp_core/test_threading.py (+1/-1)
tests/functional/openlp_core/ui/media/test_systemplayer.py (+1/-1)
tests/functional/openlp_core/ui/media/test_vlcplayer.py (+1/-1)
tests/functional/openlp_core/ui/media/test_webkitplayer.py (+0/-66)
tests/functional/openlp_core/ui/test_exceptionform.py (+42/-56)
tests/functional/openlp_core/ui/test_firsttimeform.py (+63/-38)
tests/functional/openlp_core/ui/test_formattingtagsform.py (+1/-1)
tests/functional/openlp_core/ui/test_icons.py (+0/-1)
tests/functional/openlp_core/ui/test_maindisplay.py (+0/-283)
tests/functional/openlp_core/ui/test_mainwindow.py (+23/-14)
tests/functional/openlp_core/ui/test_servicemanager.py (+15/-12)
tests/functional/openlp_core/ui/test_slidecontroller.py (+17/-21)
tests/functional/openlp_core/widgets/test_views.py (+15/-16)
tests/functional/openlp_core/widgets/test_widgets.py (+199/-0)
tests/functional/openlp_plugins/bibles/test_bibleimport.py (+1/-1)
tests/functional/openlp_plugins/bibles/test_bibleserver.py (+1/-1)
tests/functional/openlp_plugins/bibles/test_csvimport.py (+1/-0)
tests/functional/openlp_plugins/bibles/test_mediaitem.py (+2/-2)
tests/functional/openlp_plugins/bibles/test_opensongimport.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_osisimport.py (+1/-0)
tests/functional/openlp_plugins/bibles/test_swordimport.py (+5/-3)
tests/functional/openlp_plugins/bibles/test_wordprojectimport.py (+2/-1)
tests/functional/openlp_plugins/bibles/test_zefaniaimport.py (+1/-0)
tests/functional/openlp_plugins/custom/test_mediaitem.py (+2/-1)
tests/functional/openlp_plugins/images/test_imagetab.py (+1/-0)
tests/functional/openlp_plugins/images/test_upgrade.py (+1/-0)
tests/functional/openlp_plugins/media/test_mediaitem.py (+1/-0)
tests/functional/openlp_plugins/presentations/test_impresscontroller.py (+3/-3)
tests/functional/openlp_plugins/presentations/test_mediaitem.py (+1/-1)
tests/functional/openlp_plugins/presentations/test_messagelistener.py (+1/-1)
tests/functional/openlp_plugins/presentations/test_pdfcontroller.py (+5/-5)
tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py (+3/-2)
tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py.THIS (+224/-0)
tests/functional/openlp_plugins/presentations/test_presentationcontroller.py (+1/-0)
tests/functional/openlp_plugins/songs/test_chordproimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_db.py (+1/-1)
tests/functional/openlp_plugins/songs/test_easyslidesimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_editsongform.py (+1/-1)
tests/functional/openlp_plugins/songs/test_editverseform.py (+1/-0)
tests/functional/openlp_plugins/songs/test_ewimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_foilpresenterimport.py (+1/-1)
tests/functional/openlp_plugins/songs/test_lib.py (+2/-2)
tests/functional/openlp_plugins/songs/test_lyriximport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_mediashout.py (+2/-1)
tests/functional/openlp_plugins/songs/test_openlpimporter.py (+1/-1)
tests/functional/openlp_plugins/songs/test_openlyricsimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_openoffice.py (+1/-0)
tests/functional/openlp_plugins/songs/test_opensongimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_opsproimport.py (+5/-3)
tests/functional/openlp_plugins/songs/test_powerpraiseimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_propresenterimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songbeamerimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songproimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_songselect.py (+4/-3)
tests/functional/openlp_plugins/songs/test_songshowplusimport.py (+2/-1)
tests/functional/openlp_plugins/songs/test_sundayplusimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_videopsalm.py (+2/-1)
tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_worshipassistantimport.py (+1/-0)
tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py (+3/-2)
tests/functional/openlp_plugins/songs/test_zionworximport.py (+1/-0)
tests/helpers/songfileimport.py (+2/-1)
tests/interfaces/openlp_core/common/test_utils.py (+0/-1)
tests/interfaces/openlp_core/ui/test_servicemanager.py (+3/-3)
tests/interfaces/openlp_core/ui/test_settings_form.py (+1/-0)
tests/interfaces/openlp_core/ui/test_thememanager.py (+1/-1)
tests/interfaces/openlp_core/widgets/test_edits.py (+1/-1)
tests/interfaces/openlp_core/widgets/test_views.py (+5/-0)
tests/interfaces/openlp_plugins/bibles/test_lib_http.py (+1/-1)
tests/interfaces/openlp_plugins/custom/forms/test_customform.py (+1/-1)
tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py (+9/-7)
tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py (+1/-0)
tests/interfaces/openlp_plugins/songs/forms/test_editverseform.py (+1/-0)
tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py (+1/-1)
tests/js/fake_webchannel.js (+5/-0)
tests/js/polyfill.js (+84/-0)
tests/js/test_display.js (+632/-0)
tests/openlp_core/common/test_network_interfaces.py (+0/-1)
tests/openlp_core/projectors/test_projector_bugfixes_01.py (+1/-0)
tests/openlp_core/projectors/test_projector_constants.py (+1/-0)
tests/openlp_core/projectors/test_projector_db.py (+1/-1)
tests/openlp_core/projectors/test_projector_editform.py (+1/-1)
tests/openlp_core/projectors/test_projector_pjlink_base_01.py (+3/-5)
tests/openlp_core/projectors/test_projector_pjlink_base_02.py (+0/-1)
tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+2/-3)
tests/openlp_core/projectors/test_projector_pjlink_commands_01.py (+2/-12)
tests/openlp_core/projectors/test_projector_pjlink_commands_02.py (+2/-2)
tests/openlp_core/projectors/test_projector_pjlink_udp.py (+1/-1)
tests/openlp_core/projectors/test_projector_sourceform.py (+3/-3)
tests/openlp_core/projectors/test_projector_utilities.py (+3/-2)
tests/openlp_core/projectors/test_projectormanager.py (+1/-1)
tests/utils/__init__.py (+7/-10)
tests/utils/constants.py (+1/-0)
tests/utils/test_bzr_tags.py (+2/-1)
tests/utils/test_pylint.py (+4/-2)
To merge this branch: bzr merge lp:~openlp-dev/openlp/webengine-migrate
Reviewer Review Type Date Requested Status
OpenLP Core Pending
Review via email: mp+363088@code.launchpad.net

This proposal supersedes a proposal from 2019-02-12.

This proposal has been superseded by a proposal from 2019-02-12.

Commit message

Migration from WebKit to Webengine. Also introduced reveal.js for slide rendering, new screen setup dialogs and many other changes.

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/73/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/75/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/76/ for more details

2878. By Tim Bentley

fix up tests

2879. By Tomas Groth

Fix more tests

2880. By Tomas Groth

pep8 fixes

2881. By Tomas Groth

More pep8 fixes

2882. By Tomas Groth

Even more pep8 fixes and removed the old htmlbuilder.

2883. By Raoul Snyman

Fix an unused import and some incorrect patch()s

2884. By Raoul Snyman

HEAD

2885. By Tomas Groth

trunk

Unmerged revisions

2885. By Tomas Groth

trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2018-06-26 17:10:34 +0000
3+++ .bzrignore 2019-02-12 20:56:20 +0000
4@@ -45,4 +45,11 @@
5 resources/windows/warnOpenLP.txt
6 *.ropeproject
7 tags
8+output
9+htmlcov
10+node_modules
11+openlp-test-projectordb.sqlite
12+package-lock.json
13+.cache
14+test
15 tests.kdev4
16
17=== added file 'karma.conf.js'
18--- karma.conf.js 1970-01-01 00:00:00 +0000
19+++ karma.conf.js 2019-02-12 20:56:20 +0000
20@@ -0,0 +1,77 @@
21+module.exports = function(config) {
22+ config.set({
23+ // base path that will be used to resolve all patterns (eg. files, exclude)
24+ basePath: "",
25+
26+ // frameworks to use
27+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
28+ frameworks: ["jasmine"],
29+
30+ // list of files / patterns to load in the browser
31+ files: [
32+ "tests/js/polyfill.js",
33+ "tests/js/fake_webchannel.js",
34+ "openlp/core/display/html/reveal.js",
35+ "openlp/core/display/html/display.js",
36+ "tests/js/test_*.js"
37+ ],
38+
39+ // list of files to exclude
40+ exclude: [
41+ ],
42+
43+ // preprocess matching files before serving them to the browser
44+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
45+ preprocessors: {
46+ // source files, that you wanna generate coverage for
47+ // do not include tests or libraries
48+ // (these files will be instrumented by Istanbul)
49+ "display.js": ["coverage"]
50+ },
51+
52+ // test results reporter to use
53+ // possible values: "dots", "progress"
54+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
55+ reporters: ["progress", "coverage"],
56+
57+ // configure the coverateReporter
58+ coverageReporter: {
59+ type : "html",
60+ dir : "htmlcov/"
61+ },
62+
63+ // web server port
64+ port: 9876,
65+
66+ // enable / disable colors in the output (reporters and logs)
67+ colors: true,
68+
69+ // level of logging
70+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
71+ logLevel: config.LOG_DEBUG,
72+
73+ // loggers
74+ /* loggers: [
75+ {"type": "file", "filename": "karma.log"}
76+ ],*/
77+
78+ // enable / disable watching file and executing tests whenever any file changes
79+ autoWatch: true,
80+
81+ // start these browsers
82+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
83+ browsers: ["PhantomJS"],
84+
85+ // Continuous Integration mode
86+ // if true, Karma captures browsers, runs the tests and exits
87+ singleRun: false,
88+
89+ // Concurrency level
90+ // how many browser should be started simultaneous
91+ concurrency: Infinity,
92+
93+ client: {
94+ captureConsole: true
95+ }
96+ })
97+}
98
99=== modified file 'nose2.cfg'
100--- nose2.cfg 2017-11-16 05:03:19 +0000
101+++ nose2.cfg 2019-02-12 20:56:20 +0000
102@@ -1,5 +1,5 @@
103 [unittest]
104-verbose = True
105+verbose = true
106 plugins = nose2.plugins.mp
107
108 [log-capture]
109@@ -9,19 +9,19 @@
110 log-level = ERROR
111
112 [test-result]
113-always-on = True
114-descriptions = True
115+always-on = true
116+descriptions = true
117
118 [coverage]
119-always-on = True
120+always-on = true
121 coverage = openlp
122 coverage-report = html
123
124 [multiprocess]
125-always-on = False
126+always-on = false
127 processes = 4
128
129 [output-buffer]
130-always-on = True
131-stderr = True
132-stdout = False
133+always-on = true
134+stderr = true
135+stdout = true
136
137=== modified file 'openlp/.version'
138--- openlp/.version 2019-01-11 20:23:24 +0000
139+++ openlp/.version 2019-02-12 20:56:20 +0000
140@@ -1,1 +1,1 @@
141-2.9.0
142+2.5.dev2856
143\ No newline at end of file
144
145=== modified file 'openlp/core/api/deploy.py'
146--- openlp/core/api/deploy.py 2018-01-04 07:00:55 +0000
147+++ openlp/core/api/deploy.py 2019-02-12 20:56:20 +0000
148@@ -25,7 +25,7 @@
149 from zipfile import ZipFile
150
151 from openlp.core.common.applocation import AppLocation
152-from openlp.core.common.httputils import download_file, get_web_page, get_url_file_size
153+from openlp.core.common.httputils import download_file, get_url_file_size, get_web_page
154 from openlp.core.common.registry import Registry
155
156
157
158=== modified file 'openlp/core/api/endpoint/controller.py'
159--- openlp/core/api/endpoint/controller.py 2018-08-25 14:08:19 +0000
160+++ openlp/core/api/endpoint/controller.py 2019-02-12 20:56:20 +0000
161@@ -34,6 +34,7 @@
162 from openlp.core.lib import create_thumb
163 from openlp.core.lib.serviceitem import ItemCapabilities
164
165+
166 log = logging.getLogger(__name__)
167
168 controller_endpoint = Endpoint('controller')
169@@ -48,7 +49,7 @@
170
171 :param request: the http request - not used
172 """
173- log.debug("controller_text ")
174+ log.debug('controller_text')
175 live_controller = Registry().get('live_controller')
176 current_item = live_controller.service_item
177 data = []
178@@ -57,13 +58,14 @@
179 item = {}
180 # Handle text (songs, custom, bibles)
181 if current_item.is_text():
182- if frame['verseTag']:
183- item['tag'] = str(frame['verseTag'])
184+ if frame['verse']:
185+ item['tag'] = str(frame['verse'])
186 else:
187 item['tag'] = str(index + 1)
188- item['chords_text'] = str(frame['chords_text'])
189- item['text'] = str(frame['text'])
190- item['html'] = str(frame['html'])
191+ # TODO: Figure out rendering chords
192+ item['chords_text'] = str(frame.get('chords_text', ''))
193+ item['text'] = frame['text']
194+ item['html'] = current_item.get_rendered_frame(index)
195 # Handle images, unless a custom thumbnail is given or if thumbnails is disabled
196 elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
197 item['tag'] = str(index + 1)
198
199=== modified file 'openlp/core/api/endpoint/core.py'
200--- openlp/core/api/endpoint/core.py 2018-08-25 14:08:19 +0000
201+++ openlp/core/api/endpoint/core.py 2019-02-12 20:56:20 +0000
202@@ -30,8 +30,8 @@
203 from openlp.core.common.i18n import UiStrings, translate
204 from openlp.core.common.registry import Registry
205 from openlp.core.lib import image_to_byte
206-from openlp.core.lib.plugin import StringContent
207-from openlp.core.lib.plugin import PluginStatus
208+from openlp.core.lib.plugin import PluginStatus, StringContent
209+
210
211 template_dir = 'templates'
212 static_dir = 'static'
213
214=== modified file 'openlp/core/api/endpoint/remote.py'
215--- openlp/core/api/endpoint/remote.py 2017-12-29 09:15:48 +0000
216+++ openlp/core/api/endpoint/remote.py 2019-02-12 20:56:20 +0000
217@@ -24,6 +24,7 @@
218 from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
219 from openlp.core.api.http.endpoint import Endpoint
220
221+
222 log = logging.getLogger(__name__)
223
224 remote_endpoint = Endpoint('remote', template_dir='remotes')
225
226=== modified file 'openlp/core/api/endpoint/service.py'
227--- openlp/core/api/endpoint/service.py 2017-12-29 09:15:48 +0000
228+++ openlp/core/api/endpoint/service.py 2019-02-12 20:56:20 +0000
229@@ -26,6 +26,7 @@
230 from openlp.core.api.http.endpoint import Endpoint
231 from openlp.core.common.registry import Registry
232
233+
234 log = logging.getLogger(__name__)
235
236 service_endpoint = Endpoint('service')
237
238=== modified file 'openlp/core/api/http/server.py'
239--- openlp/core/api/http/server.py 2018-01-07 04:36:45 +0000
240+++ openlp/core/api/http/server.py 2019-02-12 20:56:20 +0000
241@@ -30,22 +30,21 @@
242 from waitress.server import create_server
243
244 from openlp.core.api.deploy import download_and_check, download_sha256
245-from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
246-from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
247+from openlp.core.api.endpoint.controller import api_controller_endpoint, controller_endpoint
248+from openlp.core.api.endpoint.core import blank_endpoint, chords_endpoint, main_endpoint, stage_endpoint
249 from openlp.core.api.endpoint.remote import remote_endpoint
250-from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
251-from openlp.core.api.http import application
252-from openlp.core.api.http import register_endpoint
253+from openlp.core.api.endpoint.service import api_service_endpoint, service_endpoint
254+from openlp.core.api.http import application, register_endpoint
255 from openlp.core.api.poll import Poller
256 from openlp.core.common.applocation import AppLocation
257-from openlp.core.common.i18n import UiStrings
258-from openlp.core.common.i18n import translate
259+from openlp.core.common.i18n import UiStrings, translate
260 from openlp.core.common.mixins import LogMixin, RegistryProperties
261 from openlp.core.common.path import create_paths
262 from openlp.core.common.registry import Registry, RegistryBase
263 from openlp.core.common.settings import Settings
264 from openlp.core.threading import ThreadWorker, run_thread
265
266+
267 log = logging.getLogger(__name__)
268
269
270
271=== modified file 'openlp/core/api/http/wsgiapp.py'
272--- openlp/core/api/http/wsgiapp.py 2018-07-04 20:42:55 +0000
273+++ openlp/core/api/http/wsgiapp.py 2019-02-12 20:56:20 +0000
274@@ -33,6 +33,7 @@
275 from openlp.core.api.http.errors import HttpError, NotFound, ServerError
276 from openlp.core.common.applocation import AppLocation
277
278+
279 ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
280
281 log = logging.getLogger(__name__)
282
283=== modified file 'openlp/core/api/tab.py'
284--- openlp/core/api/tab.py 2018-08-25 14:08:19 +0000
285+++ openlp/core/api/tab.py 2019-02-12 20:56:20 +0000
286@@ -31,6 +31,7 @@
287 from openlp.core.lib.settingstab import SettingsTab
288 from openlp.core.ui.icons import UiIcons
289
290+
291 ZERO_URL = '0.0.0.0'
292
293
294@@ -43,9 +44,9 @@
295 advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
296 super(ApiTab, self).__init__(parent, 'api', advanced_translated)
297
298- def setupUi(self):
299+ def setup_ui(self):
300 self.setObjectName('ApiTab')
301- super(ApiTab, self).setupUi()
302+ super(ApiTab, self).setup_ui()
303 self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
304 self.server_settings_group_box.setObjectName('server_settings_group_box')
305 self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
306@@ -154,7 +155,7 @@
307 self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
308 self.address_edit.textChanged.connect(self.set_urls)
309
310- def retranslateUi(self):
311+ def retranslate_ui(self):
312 self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface')
313 self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
314 self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
315
316=== modified file 'openlp/core/api/websockets.py'
317--- openlp/core/api/websockets.py 2018-01-07 17:50:29 +0000
318+++ openlp/core/api/websockets.py 2019-02-12 20:56:20 +0000
319@@ -35,6 +35,7 @@
320 from openlp.core.common.settings import Settings
321 from openlp.core.threading import ThreadWorker, run_thread
322
323+
324 log = logging.getLogger(__name__)
325
326
327
328=== modified file 'openlp/core/app.py'
329--- openlp/core/app.py 2018-10-25 16:38:39 +0000
330+++ openlp/core/app.py 2019-02-12 20:56:20 +0000
331@@ -33,27 +33,28 @@
332 from datetime import datetime
333 from traceback import format_exception
334
335-from PyQt5 import QtCore, QtWidgets
336+from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets # noqa
337
338 from openlp.core.state import State
339 from openlp.core.common import is_macosx, is_win
340 from openlp.core.common.applocation import AppLocation
341 from openlp.core.loader import loader
342 from openlp.core.common.i18n import LanguageManager, UiStrings, translate
343-from openlp.core.common.path import create_paths, copytree
344+from openlp.core.common.path import copytree, create_paths
345 from openlp.core.common.registry import Registry
346 from openlp.core.common.settings import Settings
347 from openlp.core.display.screens import ScreenList
348 from openlp.core.resources import qInitResources
349-from openlp.core.ui.splashscreen import SplashScreen
350+from openlp.core.server import Server
351 from openlp.core.ui.exceptionform import ExceptionForm
352 from openlp.core.ui.firsttimeform import FirstTimeForm
353 from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
354 from openlp.core.ui.mainwindow import MainWindow
355+from openlp.core.ui.splashscreen import SplashScreen
356 from openlp.core.ui.style import get_application_stylesheet
357-from openlp.core.server import Server
358 from openlp.core.version import check_for_update, get_version
359
360+
361 __all__ = ['OpenLP', 'main']
362
363
364@@ -74,7 +75,8 @@
365 """
366 self.is_event_loop_active = True
367 result = QtWidgets.QApplication.exec()
368- self.server.close_server()
369+ if hasattr(self, 'server'):
370+ self.server.close_server()
371 return result
372
373 def run(self, args):
374@@ -317,7 +319,7 @@
375 file_path = log_path / 'openlp.log'
376 # TODO: FileHandler accepts a Path object in Py3.6
377 logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
378- logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
379+ logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s'))
380 log.addHandler(logfile)
381 if log.isEnabledFor(logging.DEBUG):
382 print('Logging to: {name}'.format(name=file_path))
383@@ -330,7 +332,8 @@
384 :param args: Some args
385 """
386 args = parse_options(args)
387- qt_args = []
388+ qt_args = ['--disable-web-security']
389+ # qt_args = []
390 if args and args.loglevel.lower() in ['d', 'debug']:
391 log.setLevel(logging.DEBUG)
392 elif args and args.loglevel.lower() in ['w', 'warning']:
393
394=== modified file 'openlp/core/common/actions.py'
395--- openlp/core/common/actions.py 2018-10-13 10:24:01 +0000
396+++ openlp/core/common/actions.py 2019-02-12 20:56:20 +0000
397@@ -29,6 +29,7 @@
398
399 from openlp.core.common.settings import Settings
400
401+
402 log = logging.getLogger(__name__)
403
404
405@@ -113,7 +114,6 @@
406 if item[1] == action:
407 self.actions.remove(item)
408 return
409- log.warning('Action "{action}" does not exist.'.format(action=action))
410
411
412 class CategoryList(object):
413
414=== modified file 'openlp/core/common/applocation.py'
415--- openlp/core/common/applocation.py 2018-10-07 23:34:00 +0000
416+++ openlp/core/common/applocation.py 2019-02-12 20:56:20 +0000
417@@ -29,10 +29,11 @@
418 import appdirs
419
420 import openlp
421-from openlp.core.common import get_frozen_path, is_win, is_macosx
422+from openlp.core.common import get_frozen_path, is_macosx, is_win
423 from openlp.core.common.path import Path, create_paths
424 from openlp.core.common.settings import Settings
425
426+
427 log = logging.getLogger(__name__)
428
429 FROZEN_APP_PATH = Path(sys.argv[0]).parent
430
431=== modified file 'openlp/core/common/db.py'
432--- openlp/core/common/db.py 2017-12-29 09:15:48 +0000
433+++ openlp/core/common/db.py 2019-02-12 20:56:20 +0000
434@@ -27,6 +27,7 @@
435
436 import sqlalchemy
437
438+
439 log = logging.getLogger(__name__)
440
441
442
443=== modified file 'openlp/core/common/httputils.py'
444--- openlp/core/common/httputils.py 2018-06-08 20:55:20 +0000
445+++ openlp/core/common/httputils.py 2019-02-12 20:56:20 +0000
446@@ -34,6 +34,7 @@
447 from openlp.core.common.registry import Registry
448 from openlp.core.common.settings import ProxyMode, Settings
449
450+
451 log = logging.getLogger(__name__ + '.__init__')
452
453 USER_AGENTS = {
454
455=== modified file 'openlp/core/common/i18n.py'
456--- openlp/core/common/i18n.py 2018-10-27 11:05:41 +0000
457+++ openlp/core/common/i18n.py 2019-02-12 20:56:20 +0000
458@@ -29,10 +29,11 @@
459
460 from PyQt5 import QtCore, QtWidgets
461
462-from openlp.core.common import is_win, is_macosx
463+from openlp.core.common import is_macosx, is_win
464 from openlp.core.common.applocation import AppLocation
465 from openlp.core.common.settings import Settings
466
467+
468 log = logging.getLogger(__name__)
469
470
471
472=== modified file 'openlp/core/common/mixins.py'
473--- openlp/core/common/mixins.py 2018-11-18 17:29:47 +0000
474+++ openlp/core/common/mixins.py 2019-02-12 20:56:20 +0000
475@@ -28,6 +28,7 @@
476 from openlp.core.common import is_win, trace_error_handler
477 from openlp.core.common.registry import Registry
478
479+
480 DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
481 'preview_size_changed', 'resizeEvent']
482
483
484=== modified file 'openlp/core/common/path.py'
485--- openlp/core/common/path.py 2018-08-12 11:14:47 +0000
486+++ openlp/core/common/path.py 2019-02-12 20:56:20 +0000
487@@ -25,6 +25,7 @@
488
489 from openlp.core.common import is_win
490
491+
492 if is_win():
493 from pathlib import WindowsPath as PathVariant # pragma: nocover
494 else:
495
496=== modified file 'openlp/core/common/registry.py'
497--- openlp/core/common/registry.py 2018-10-25 16:38:39 +0000
498+++ openlp/core/common/registry.py 2019-02-12 20:56:20 +0000
499@@ -27,6 +27,7 @@
500
501 from openlp.core.common import de_hump, trace_error_handler
502
503+
504 log = logging.getLogger(__name__)
505
506
507@@ -143,6 +144,7 @@
508 if event in self.functions_list:
509 for function in self.functions_list[event]:
510 try:
511+ log.debug('Running function {} for {}'.format(function, event))
512 result = function(*args, **kwargs)
513 if result:
514 results.append(result)
515
516=== modified file 'openlp/core/common/settings.py'
517--- openlp/core/common/settings.py 2018-10-24 19:35:22 +0000
518+++ openlp/core/common/settings.py 2019-02-12 20:56:20 +0000
519@@ -1,4 +1,4 @@
520-# -*- coding: utf-8 -*-
521+ # -*- coding: utf-8 -*-
522 # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
523
524 ###############################################################################
525@@ -33,7 +33,8 @@
526
527 from openlp.core.common import SlideLimits, ThemeLevel, is_linux, is_win
528 from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
529-from openlp.core.common.path import Path, str_to_path, files_to_paths
530+from openlp.core.common.path import Path, files_to_paths, str_to_path
531+
532
533 log = logging.getLogger(__name__)
534
535@@ -70,6 +71,34 @@
536 return string
537
538
539+def upgrade_screens(number, x_position, y_position, height, width, can_override, is_display_screen):
540+ """
541+ Upgrade them monitor setting from a few single entries to a composite JSON entry
542+
543+ :param int number: The old monitor number
544+ :param int x_position: The X position
545+ :param int y_position: The Y position
546+ :param bool can_override: Are the screen positions overridden
547+ :param bool is_display_screen: Is this a display screen
548+ :returns dict: Dictionary with the new value
549+ """
550+ geometry_key = 'geometry'
551+ if can_override:
552+ geometry_key = 'custom_geometry'
553+ return {
554+ number: {
555+ 'number': number,
556+ geometry_key: {
557+ 'x': x_position,
558+ 'y': y_position,
559+ 'height': height,
560+ 'width': width
561+ },
562+ 'is_display': is_display_screen
563+ }
564+ }
565+
566+
567 class Settings(QtCore.QSettings):
568 """
569 Class to wrap QSettings.
570@@ -175,6 +204,7 @@
571 # circular dependency.
572 'core/display on monitor': True,
573 'core/override position': False,
574+ 'core/monitor': {},
575 'core/application version': '0.0',
576 'images/background color': '#000000',
577 'media/players': 'system,webkit',
578@@ -276,6 +306,8 @@
579 ('songuasge/db hostname', 'songusage/db hostname', []),
580 ('songuasge/db database', 'songusage/db database', []),
581 ('presentations / Powerpoint Viewer', '', []),
582+ (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override',
583+ 'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]),
584 ('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4!
585 ('bibles/proxy address', '', []),
586 ('bibles/proxy username', '', []),
587@@ -545,7 +577,7 @@
588 :param value: The value to save
589 :rtype: None
590 """
591- if isinstance(value, Path) or (isinstance(value, list) and value and isinstance(value[0], Path)):
592+ if isinstance(value, (Path, dict)) or (isinstance(value, list) and value and isinstance(value[0], Path)):
593 value = json.dumps(value, cls=OpenLPJsonEncoder)
594 super().setValue(key, value)
595
596@@ -568,8 +600,11 @@
597 # An empty list saved to the settings results in a None type being returned.
598 elif isinstance(default_value, list):
599 return []
600+ # An empty dictionary saved to the settings results in a None type being returned.
601+ elif isinstance(default_value, dict):
602+ return {}
603 elif isinstance(setting, str):
604- if '__Path__' in setting:
605+ if '__Path__' in setting or setting.startswith('{'):
606 return json.loads(setting, cls=OpenLPJsonDecoder)
607 # Convert the setting to the correct type.
608 if isinstance(default_value, bool):
609@@ -578,6 +613,8 @@
610 # Sometimes setting is string instead of a boolean.
611 return setting == 'true'
612 if isinstance(default_value, int):
613+ if setting is None:
614+ return 0
615 return int(setting)
616 return setting
617
618
619=== added directory 'openlp/core/display/html'
620=== added file 'openlp/core/display/html/black.css'
621--- openlp/core/display/html/black.css 1970-01-01 00:00:00 +0000
622+++ openlp/core/display/html/black.css 2019-02-12 20:56:20 +0000
623@@ -0,0 +1,292 @@
624+/**
625+ * Black theme for reveal.js. This is the opposite of the 'white' theme.
626+ *
627+ * By Hakim El Hattab, http://hakim.se
628+ */
629+@import url(../../lib/font/source-sans-pro/source-sans-pro.css);
630+section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
631+ color: #222; }
632+
633+/*********************************************
634+ * GLOBAL STYLES
635+ *********************************************/
636+body {
637+ background: #222;
638+ background-color: #222; }
639+
640+.reveal {
641+ font-family: "Source Sans Pro", Helvetica, sans-serif;
642+ font-size: 42px;
643+ font-weight: normal;
644+ color: #fff; }
645+
646+::selection {
647+ color: #fff;
648+ background: #bee4fd;
649+ text-shadow: none; }
650+
651+::-moz-selection {
652+ color: #fff;
653+ background: #bee4fd;
654+ text-shadow: none; }
655+
656+.reveal .slides > section,
657+.reveal .slides > section > section {
658+ line-height: 1.3;
659+ font-weight: inherit; }
660+
661+/*********************************************
662+ * HEADERS
663+ *********************************************/
664+.reveal h1,
665+.reveal h2,
666+.reveal h3,
667+.reveal h4,
668+.reveal h5,
669+.reveal h6 {
670+ margin: 0 0 20px 0;
671+ color: #fff;
672+ font-family: "Source Sans Pro", Helvetica, sans-serif;
673+ font-weight: 600;
674+ line-height: 1.2;
675+ letter-spacing: normal;
676+ text-transform: uppercase;
677+ text-shadow: none;
678+ word-wrap: break-word; }
679+
680+.reveal h1 {
681+ font-size: 2.5em; }
682+
683+.reveal h2 {
684+ font-size: 1.6em; }
685+
686+.reveal h3 {
687+ font-size: 1.3em; }
688+
689+.reveal h4 {
690+ font-size: 1em; }
691+
692+.reveal h1 {
693+ text-shadow: none; }
694+
695+/*********************************************
696+ * OTHER
697+ *********************************************/
698+.reveal p {
699+ margin: 20px 0;
700+ line-height: 1.3; }
701+
702+/* Ensure certain elements are never larger than the slide itself */
703+.reveal img,
704+.reveal video,
705+.reveal iframe {
706+ max-width: 95%;
707+ max-height: 95%; }
708+
709+.reveal strong,
710+.reveal b {
711+ font-weight: bold; }
712+
713+.reveal em {
714+ font-style: italic; }
715+
716+.reveal ol,
717+.reveal dl,
718+.reveal ul {
719+ display: inline-block;
720+ text-align: left;
721+ margin: 0 0 0 1em; }
722+
723+.reveal ol {
724+ list-style-type: decimal; }
725+
726+.reveal ul {
727+ list-style-type: disc; }
728+
729+.reveal ul ul {
730+ list-style-type: square; }
731+
732+.reveal ul ul ul {
733+ list-style-type: circle; }
734+
735+.reveal ul ul,
736+.reveal ul ol,
737+.reveal ol ol,
738+.reveal ol ul {
739+ display: block;
740+ margin-left: 40px; }
741+
742+.reveal dt {
743+ font-weight: bold; }
744+
745+.reveal dd {
746+ margin-left: 40px; }
747+
748+.reveal q,
749+.reveal blockquote {
750+ quotes: none; }
751+
752+.reveal blockquote {
753+ display: block;
754+ position: relative;
755+ width: 70%;
756+ margin: 20px auto;
757+ padding: 5px;
758+ font-style: italic;
759+ background: rgba(255, 255, 255, 0.05);
760+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); }
761+
762+.reveal blockquote p:first-child,
763+.reveal blockquote p:last-child {
764+ display: inline-block; }
765+
766+.reveal q {
767+ font-style: italic; }
768+
769+.reveal pre {
770+ display: block;
771+ position: relative;
772+ width: 90%;
773+ margin: 20px auto;
774+ text-align: left;
775+ font-size: 0.55em;
776+ font-family: monospace;
777+ line-height: 1.2em;
778+ word-wrap: break-word;
779+ box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
780+
781+.reveal code {
782+ font-family: monospace; }
783+
784+.reveal pre code {
785+ display: block;
786+ padding: 5px;
787+ overflow: auto;
788+ max-height: 400px;
789+ word-wrap: normal; }
790+
791+.reveal table {
792+ margin: auto;
793+ border-collapse: collapse;
794+ border-spacing: 0; }
795+
796+.reveal table th {
797+ font-weight: bold; }
798+
799+.reveal table th,
800+.reveal table td {
801+ text-align: left;
802+ padding: 0.2em 0.5em 0.2em 0.5em;
803+ border-bottom: 1px solid; }
804+
805+.reveal table th[align="center"],
806+.reveal table td[align="center"] {
807+ text-align: center; }
808+
809+.reveal table th[align="right"],
810+.reveal table td[align="right"] {
811+ text-align: right; }
812+
813+.reveal table tbody tr:last-child th,
814+.reveal table tbody tr:last-child td {
815+ border-bottom: none; }
816+
817+.reveal sup {
818+ vertical-align: super; }
819+
820+.reveal sub {
821+ vertical-align: sub; }
822+
823+.reveal small {
824+ display: inline-block;
825+ font-size: 0.6em;
826+ line-height: 1.2em;
827+ vertical-align: top; }
828+
829+.reveal small * {
830+ vertical-align: top; }
831+
832+/*********************************************
833+ * LINKS
834+ *********************************************/
835+.reveal a {
836+ color: #42affa;
837+ text-decoration: none;
838+ -webkit-transition: color .15s ease;
839+ -moz-transition: color .15s ease;
840+ transition: color .15s ease; }
841+
842+.reveal a:hover {
843+ color: #8dcffc;
844+ text-shadow: none;
845+ border: none; }
846+
847+.reveal .roll span:after {
848+ color: #fff;
849+ background: #068de9; }
850+
851+/*********************************************
852+ * IMAGES
853+ *********************************************/
854+.reveal section img {
855+ margin: 15px 0px;
856+ background: rgba(255, 255, 255, 0.12);
857+ border: 4px solid #fff;
858+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); }
859+
860+.reveal section img.plain {
861+ border: 0;
862+ box-shadow: none; }
863+
864+.reveal a img {
865+ -webkit-transition: all .15s linear;
866+ -moz-transition: all .15s linear;
867+ transition: all .15s linear; }
868+
869+.reveal a:hover img {
870+ background: rgba(255, 255, 255, 0.2);
871+ border-color: #42affa;
872+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); }
873+
874+/*********************************************
875+ * NAVIGATION CONTROLS
876+ *********************************************/
877+.reveal .controls .navigate-left,
878+.reveal .controls .navigate-left.enabled {
879+ border-right-color: #42affa; }
880+
881+.reveal .controls .navigate-right,
882+.reveal .controls .navigate-right.enabled {
883+ border-left-color: #42affa; }
884+
885+.reveal .controls .navigate-up,
886+.reveal .controls .navigate-up.enabled {
887+ border-bottom-color: #42affa; }
888+
889+.reveal .controls .navigate-down,
890+.reveal .controls .navigate-down.enabled {
891+ border-top-color: #42affa; }
892+
893+.reveal .controls .navigate-left.enabled:hover {
894+ border-right-color: #8dcffc; }
895+
896+.reveal .controls .navigate-right.enabled:hover {
897+ border-left-color: #8dcffc; }
898+
899+.reveal .controls .navigate-up.enabled:hover {
900+ border-bottom-color: #8dcffc; }
901+
902+.reveal .controls .navigate-down.enabled:hover {
903+ border-top-color: #8dcffc; }
904+
905+/*********************************************
906+ * PROGRESS BAR
907+ *********************************************/
908+.reveal .progress {
909+ background: rgba(0, 0, 0, 0.2); }
910+
911+.reveal .progress span {
912+ background: #42affa;
913+ -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
914+ -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
915+ transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
916
917=== added file 'openlp/core/display/html/checkerboard.png'
918Binary files openlp/core/display/html/checkerboard.png 1970-01-01 00:00:00 +0000 and openlp/core/display/html/checkerboard.png 2019-02-12 20:56:20 +0000 differ
919=== added file 'openlp/core/display/html/display.html'
920--- openlp/core/display/html/display.html 1970-01-01 00:00:00 +0000
921+++ openlp/core/display/html/display.html 2019-02-12 20:56:20 +0000
922@@ -0,0 +1,39 @@
923+<!DOCTYPE html>
924+<html>
925+ <head>
926+ <title>Display Window</title>
927+ <link href="reveal.css" rel="stylesheet">
928+ <style type="text/css">
929+ body {
930+ background: transparent !important;
931+ color: #fff !important;
932+ }
933+ sup {
934+ vertical-align: super !important;
935+ font-size: smaller !important;
936+ }
937+ .reveal .slides > section,
938+ .reveal .slides > section > section {
939+ padding: 0;
940+ }
941+ .reveal > .backgrounds > .present {
942+ visibility: hidden !important;
943+ }
944+ #global-background {
945+ display: block;
946+ visibility: visible;
947+ z-index: -1;
948+ }
949+ </style>
950+ <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
951+ <script type="text/javascript" src="reveal.js"></script>
952+ <script type="text/javascript" src="display.js"></script>
953+ </head>
954+ <body>
955+ <div class="reveal">
956+ <div id="global-background" class="slide-background present" data-loaded="true"></div>
957+ <div class="slides"></div>
958+ <div class="footer"></div>
959+ </div>
960+ </body>
961+</html>
962
963=== added file 'openlp/core/display/html/display.js'
964--- openlp/core/display/html/display.js 1970-01-01 00:00:00 +0000
965+++ openlp/core/display/html/display.js 2019-02-12 20:56:20 +0000
966@@ -0,0 +1,789 @@
967+/**
968+ * display.js is the main Javascript file that is used to drive the display.
969+ */
970+
971+/**
972+ * Background type enumeration
973+ */
974+var BackgroundType = {
975+ Transparent: "transparent",
976+ Solid: "solid",
977+ Gradient: "gradient",
978+ Video: "video",
979+ Image: "image"
980+};
981+
982+/**
983+ * Gradient type enumeration
984+ */
985+var GradientType = {
986+ Horizontal: "horizontal",
987+ LeftTop: "leftTop",
988+ LeftBottom: "leftBottom",
989+ Vertical: "vertical",
990+ Circular: "circular"
991+};
992+
993+/**
994+ * Horizontal alignment enumeration
995+ */
996+var HorizontalAlign = {
997+ Left: "left",
998+ Right: "right",
999+ Center: "center",
1000+ Justify: "justify"
1001+};
1002+
1003+/**
1004+ * Vertical alignment enumeration
1005+ */
1006+var VerticalAlign = {
1007+ Top: "top",
1008+ Middle: "middle",
1009+ Bottom: "bottom"
1010+};
1011+
1012+/**
1013+ * Audio state enumeration
1014+ */
1015+var AudioState = {
1016+ Playing: "playing",
1017+ Paused: "paused",
1018+ Stopped: "stopped"
1019+};
1020+
1021+/**
1022+ * Return an array of elements based on the selector query
1023+ * @param {string} selector - The selector to find elements
1024+ * @returns {array} An array of matching elements
1025+ */
1026+function $(selector) {
1027+ return Array.from(document.querySelectorAll(selector));
1028+}
1029+
1030+/**
1031+ * Build linear gradient CSS
1032+ * @private
1033+ * @param {string} startDir - Starting direction
1034+ * @param {string} endDir - Ending direction
1035+ * @param {string} startColor - The starting color
1036+ * @param {string} endColor - The ending color
1037+ * @returns {string} A string of the gradient CSS
1038+ */
1039+function _buildLinearGradient(startDir, endDir, startColor, endColor) {
1040+ return "-webkit-gradient(linear, " + startDir + ", " + endDir + ", from(" + startColor + "), to(" + endColor + ")) fixed";
1041+}
1042+
1043+/**
1044+ * Build radial gradient CSS
1045+ * @private
1046+ * @param {string} width - Width of the gradient
1047+ * @param {string} startColor - The starting color
1048+ * @param {string} endColor - The ending color
1049+ * @returns {string} A string of the gradient CSS
1050+ */
1051+function _buildRadialGradient(width, startColor, endColor) {
1052+ return "-webkit-gradient(radial, " + width + " 50%, 100, " + width + " 50%, " + width + ", from(" + startColor + "), to(" + endColor + ")) fixed";
1053+}
1054+
1055+/**
1056+ * Get a style value from an element (computed or manual)
1057+ * @private
1058+ * @param {Object} element - The element whose style we want
1059+ * @param {string} style - The name of the style we want
1060+ * @returns {(Number|string)} The style value (type depends on the style)
1061+ */
1062+function _getStyle(element, style) {
1063+ return document.defaultView.getComputedStyle(element).getPropertyValue(style);
1064+}
1065+
1066+/**
1067+ * Convert newlines to <br> tags
1068+ * @private
1069+ * @param {string} text - The text to parse
1070+ * @returns {string} The text now with <br> tags
1071+ */
1072+function _nl2br(text) {
1073+ return text.replace("\r\n", "\n").replace("\n", "<br>");
1074+}
1075+
1076+/**
1077+ * Prepare text by creating paragraphs and calling _nl2br to convert newlines to <br> tags
1078+ * @private
1079+ * @param {string} text - The text to parse
1080+ * @returns {string} The text now with <p> and <br> tags
1081+ */
1082+function _prepareText(text) {
1083+ return "<p>" + _nl2br(text) + "</p>";
1084+}
1085+
1086+/**
1087+ * The paths we get are JSON versions of Python Path objects, so let's just fix that.
1088+ * @private
1089+ * @param {object} path - The Path object
1090+ * @returns {string} The actual file path
1091+ */
1092+function _pathToString(path) {
1093+ var filename = path.__Path__.join("/").replace("//", "/");
1094+ if (!filename.startsWith("/")) {
1095+ filename = "/" + filename;
1096+ }
1097+ return filename;
1098+}
1099+
1100+/**
1101+ * An audio player with a play list
1102+ */
1103+var AudioPlayer = function (audioElement) {
1104+ this._audioElement = null;
1105+ this._eventListeners = {};
1106+ this._playlist = [];
1107+ this._currentTrack = null;
1108+ this._canRepeat = false;
1109+ this._state = AudioState.Stopped;
1110+ this.createAudioElement();
1111+};
1112+
1113+/**
1114+ * Call all listeners associated with this event
1115+ * @private
1116+ * @param {object} event - The event that was emitted
1117+ */
1118+AudioPlayer.prototype._callListener = function (event) {
1119+ if (this._eventListeners.hasOwnProperty(event.type)) {
1120+ this._eventListeners[event.type].forEach(function (listener) {
1121+ listener(event);
1122+ });
1123+ }
1124+ else {
1125+ console.warn("Received unknown event \"" + event.type + "\", doing nothing.");
1126+ }
1127+};
1128+
1129+/**
1130+ * Create the <audio> element that is used to play the audio
1131+ */
1132+AudioPlayer.prototype.createAudioElement = function () {
1133+ this._audioElement = document.createElement("audio");
1134+ this._audioElement.addEventListener("ended", this.onEnded);
1135+ this._audioElement.addEventListener("ended", this._callListener);
1136+ this._audioElement.addEventListener("timeupdate", this._callListener);
1137+ this._audioElement.addEventListener("volumechange", this._callListener);
1138+ this._audioElement.addEventListener("durationchange", this._callListener);
1139+ this._audioElement.addEventListener("loadeddata", this._callListener);
1140+ document.addEventListener("complete", function(event) {
1141+ document.body.appendChild(this._audioElement);
1142+ });
1143+};
1144+AudioPlayer.prototype.addEventListener = function (eventType, listener) {
1145+ this._eventListeners[eventType] = this._eventListeners[eventType] || [];
1146+ this._eventListeners[eventType].push(listener);
1147+};
1148+AudioPlayer.prototype.onEnded = function (event) {
1149+ this.nextTrack();
1150+};
1151+AudioPlayer.prototype.setCanRepeat = function (canRepeat) {
1152+ this._canRepeat = canRepeat;
1153+};
1154+AudioPlayer.prototype.clearTracks = function () {
1155+ this._playlist = [];
1156+};
1157+AudioPlayer.prototype.addTrack = function (track) {
1158+ this._playlist.push(track);
1159+};
1160+AudioPlayer.prototype.nextTrack = function () {
1161+ if (!!this._currentTrack) {
1162+ var trackIndex = this._playlist.indexOf(this._currentTrack);
1163+ if ((trackIndex + 1 >= this._playlist.length) && this._canRepeat) {
1164+ this.play(this._playlist[0]);
1165+ }
1166+ else if (trackIndex + 1 < this._playlist.length) {
1167+ this.play(this._playlist[trackIndex + 1]);
1168+ }
1169+ else {
1170+ this.stop();
1171+ }
1172+ }
1173+ else if (this._playlist.length > 0) {
1174+ this.play(this._playlist[0]);
1175+ }
1176+ else {
1177+ console.warn("No tracks in playlist, doing nothing.");
1178+ }
1179+};
1180+AudioPlayer.prototype.play = function () {
1181+ if (arguments.length > 0) {
1182+ this._currentTrack = arguments[0];
1183+ this._audioElement.src = this._currentTrack;
1184+ this._audioElement.play();
1185+ this._state = AudioState.Playing;
1186+ }
1187+ else if (this._state == AudioState.Paused) {
1188+ this._audioElement.play();
1189+ this._state = AudioState.Playing;
1190+ }
1191+ else {
1192+ console.warn("No track currently paused and no track specified, doing nothing.");
1193+ }
1194+};
1195+
1196+/**
1197+ * Pause
1198+ */
1199+AudioPlayer.prototype.pause = function () {
1200+ this._audioElement.pause();
1201+ this._state = AudioState.Paused;
1202+};
1203+
1204+/**
1205+ * Stop playing
1206+ */
1207+AudioPlayer.prototype.stop = function () {
1208+ this._audioElement.pause();
1209+ this._audioElement.src = "";
1210+ this._state = AudioState.Stopped;
1211+};
1212+
1213+/**
1214+ * The Display object is what we use from OpenLP
1215+ */
1216+var Display = {
1217+ _slides: {},
1218+ _revealConfig: {
1219+ margin: 0.0,
1220+ minScale: 1.0,
1221+ maxScale: 1.0,
1222+ controls: false,
1223+ progress: false,
1224+ history: false,
1225+ overview: false,
1226+ center: false,
1227+ help: false,
1228+ transition: "none",
1229+ backgroundTransition: "none",
1230+ viewDistance: 9999,
1231+ width: "100%",
1232+ height: "100%"
1233+ },
1234+ /**
1235+ * Start up reveal and do any other initialisation
1236+ */
1237+ init: function () {
1238+ Reveal.initialize(this._revealConfig);
1239+ },
1240+ /**
1241+ * Reinitialise Reveal
1242+ */
1243+ reinit: function () {
1244+ Reveal.reinitialize();
1245+ },
1246+ /**
1247+ * Set the transition type
1248+ * @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom"
1249+ */
1250+ setTransition: function (transitionType) {
1251+ Reveal.configure({"transition": transitionType});
1252+ },
1253+ /**
1254+ * Clear the current list of slides
1255+ */
1256+ clearSlides: function () {
1257+ $(".slides")[0].innerHTML = "";
1258+ this._slides = {};
1259+ },
1260+ /**
1261+ * Checks if the present slide content fits within the slide
1262+ */
1263+ doesContentFit: function () {
1264+ console.debug("scrollHeight: " + $(".slides")[0].scrollHeight + ", clientHeight: " + $(".slides")[0].clientHeight);
1265+ return $(".slides")[0].clientHeight >= $(".slides")[0].scrollHeight;
1266+ },
1267+ /**
1268+ * Generate the OpenLP startup splashscreen
1269+ * @param {string} bg_color - The background color
1270+ * @param {string} image - Path to the splash image
1271+ */
1272+ setStartupSplashScreen: function(bg_color, image) {
1273+ Display.clearSlides();
1274+ var globalBackground = $("#global-background")[0];
1275+ globalBackground.style.cssText = "";
1276+ globalBackground.style.setProperty("background", bg_color);
1277+ var slidesDiv = $(".slides")[0];
1278+ var section = document.createElement("section");
1279+ section.setAttribute("id", 0);
1280+ section.setAttribute("data-background", bg_color);
1281+ section.setAttribute("style", "height: 100%; width: 100%; position: relative;");
1282+ var img = document.createElement('img');
1283+ img.src = image;
1284+ img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;");
1285+ section.appendChild(img);
1286+ slidesDiv.appendChild(section);
1287+ Display._slides['0'] = 0;
1288+ Display.reinit();
1289+ },
1290+ /**
1291+ * Set fullscreen image from path
1292+ * @param {string} bg_color - The background color
1293+ * @param {string} image - Path to the image
1294+ */
1295+ setFullscreenImage: function(bg_color, image) {
1296+ Display.clearSlides();
1297+ var globalBackground = $("#global-background")[0];
1298+ globalBackground.style.cssText = "";
1299+ globalBackground.style.setProperty("background", bg_color);
1300+ var slidesDiv = $(".slides")[0];
1301+ var section = document.createElement("section");
1302+ section.setAttribute("id", 0);
1303+ section.setAttribute("data-background", bg_color);
1304+ section.setAttribute("style", "height: 100%; width: 100%;");
1305+ var img = document.createElement('img');
1306+ img.src = image;
1307+ img.setAttribute("style", "height: 100%; width: 100%");
1308+ section.appendChild(img);
1309+ slidesDiv.appendChild(section);
1310+ Display._slides['0'] = 0;
1311+ Display.reinit();
1312+ },
1313+ /**
1314+ * Set fullscreen image from base64 data
1315+ * @param {string} bg_color - The background color
1316+ * @param {string} image - Path to the image
1317+ */
1318+ setFullscreenImageFromData: function(bg_color, image_data) {
1319+ Display.clearSlides();
1320+ var globalBackground = $("#global-background")[0];
1321+ globalBackground.style.cssText = "";
1322+ globalBackground.style.setProperty("background", bg_color);
1323+ var slidesDiv = $(".slides")[0];
1324+ var section = document.createElement("section");
1325+ section.setAttribute("id", 0);
1326+ section.setAttribute("data-background", bg_color);
1327+ section.setAttribute("style", "height: 100%; width: 100%;");
1328+ var img = document.createElement('img');
1329+ img.src = 'data:image/png;base64,' + image_data;
1330+ img.setAttribute("style", "height: 100%; width: 100%");
1331+ section.appendChild(img);
1332+ slidesDiv.appendChild(section);
1333+ Display._slides['0'] = 0;
1334+ Display.reinit();
1335+ },
1336+ /**
1337+ * Display an alert
1338+ * @param {string} text - The alert text
1339+ * @param {int} location - The location of the text (top, middle or bottom)
1340+ */
1341+ alert: function (text, location) {
1342+ console.debug(" alert text: " + text, ", location: " + location);
1343+ /*
1344+ * The implementation should show an alert.
1345+ * It should be able to handle receiving a new alert before a previous one is "finished", basically queueing it.
1346+ */
1347+ return;
1348+},
1349+
1350+ /**
1351+ * Add a slides. If the slide exists but the HTML is different, update the slide.
1352+ * @param {string} verse - The verse number, e.g. "v1"
1353+ * @param {string} text - The HTML for the verse, e.g. "line1<br>line2"
1354+ * @param {string} footer_text - The HTML for the footer"
1355+ * @param {bool} [reinit=true] - Re-initialize Reveal. Defaults to true.
1356+ */
1357+ addTextSlide: function (verse, text, footer_text) {
1358+ var html = _prepareText(text);
1359+ if (this._slides.hasOwnProperty(verse)) {
1360+ var slide = $("#" + verse)[0];
1361+ if (slide.innerHTML != html) {
1362+ slide.innerHTML = html;
1363+ }
1364+ }
1365+ else {
1366+ var slidesDiv = $(".slides")[0];
1367+ var slide = document.createElement("section");
1368+ slide.setAttribute("id", verse);
1369+ slide.innerHTML = html;
1370+ slidesDiv.appendChild(slide);
1371+ var slides = $(".slides > section");
1372+ this._slides[verse] = slides.length - 1;
1373+
1374+ console.debug(" footer_text: " + footer_text);
1375+
1376+ var footerDiv = $(".footer")[0];
1377+ footerDiv.innerHTML = footer_text;
1378+ }
1379+ if ((arguments.length > 3) && (arguments[3] === true)) {
1380+ this.reinit();
1381+ }
1382+ else if (arguments.length == 3) {
1383+ this.reinit();
1384+ }
1385+ },
1386+ /**
1387+ * Set text slides.
1388+ * @param {Object[]} slides - A list of slides to add as JS objects: {"verse": "v1", "text": "line 1\nline2"}
1389+ */
1390+ setTextSlides: function (slides) {
1391+ Display.clearSlides();
1392+ slides.forEach(function (slide) {
1393+ Display.addTextSlide(slide.verse, slide.text, slide.footer, false);
1394+ });
1395+ Display.reinit();
1396+ Display.goToSlide(0);
1397+ },
1398+ /**
1399+ * Set image slides
1400+ * @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}]
1401+ */
1402+ setImageSlides: function (slides) {
1403+ Display.clearSlides();
1404+ var slidesDiv = $(".slides")[0];
1405+ slides.forEach(function (slide, index) {
1406+ var section = document.createElement("section");
1407+ section.setAttribute("id", index);
1408+ section.setAttribute("data-background", "#000");
1409+ var img = document.createElement('img');
1410+ img.src = slide["path"];
1411+ img.setAttribute("style", "height: 100%; width: 100%;");
1412+ section.appendChild(img);
1413+ slidesDiv.appendChild(section);
1414+ Display._slides[index.toString()] = index;
1415+ });
1416+ Display.reinit();
1417+ },
1418+ /**
1419+ * Set a video
1420+ * @param {Object} video - The video to show as a JS object: {"path": "url/to/file"}
1421+ */
1422+ setVideo: function (video) {
1423+ this.clearSlides();
1424+ var section = document.createElement("section");
1425+ section.setAttribute("data-background", "#000");
1426+ var videoElement = document.createElement("video");
1427+ videoElement.src = video["path"];
1428+ videoElement.preload = "auto";
1429+ videoElement.setAttribute("id", "video");
1430+ videoElement.setAttribute("style", "height: 100%; width: 100%;");
1431+ videoElement.autoplay = false;
1432+ // All the update methods below are Python functions, hence not camelCase
1433+ videoElement.addEventListener("durationchange", function (event) {
1434+ mediaWatcher.update_duration(event.target.duration);
1435+ });
1436+ videoElement.addEventListener("timeupdate", function (event) {
1437+ mediaWatcher.update_progress(event.target.currentTime);
1438+ });
1439+ videoElement.addEventListener("volumeupdate", function (event) {
1440+ mediaWatcher.update_volume(event.target.volume);
1441+ });
1442+ videoElement.addEventListener("ratechange", function (event) {
1443+ mediaWatcher.update_playback_rate(event.target.playbackRate);
1444+ });
1445+ videoElement.addEventListener("ended", function (event) {
1446+ mediaWatcher.has_ended(event.target.ended);
1447+ });
1448+ videoElement.addEventListener("muted", function (event) {
1449+ mediaWatcher.has_muted(event.target.muted);
1450+ });
1451+ section.appendChild(videoElement);
1452+ $(".slides")[0].appendChild(section);
1453+ this.reinit();
1454+ },
1455+ /**
1456+ * Play a video
1457+ */
1458+ playVideo: function () {
1459+ if ($("#video").length == 1) {
1460+ $("#video")[0].play();
1461+ }
1462+ },
1463+ /**
1464+ * Pause a video
1465+ */
1466+ pauseVideo: function () {
1467+ if ($("#video").length == 1) {
1468+ $("#video")[0].pause();
1469+ }
1470+ },
1471+ /**
1472+ * Stop a video
1473+ */
1474+ stopVideo: function () {
1475+ if ($("#video").length == 1) {
1476+ $("#video")[0].pause();
1477+ $("#video")[0].currentTime = 0.0;
1478+ }
1479+ },
1480+ /**
1481+ * Go to a particular time in a video
1482+ * @param seconds The position in seconds to seek to
1483+ */
1484+ seekVideo: function (seconds) {
1485+ if ($("#video").length == 1) {
1486+ $("#video")[0].currentTime = seconds;
1487+ }
1488+ },
1489+ /**
1490+ * Set the playback rate of a video
1491+ * @param rate A Double of the rate. 1.0 => 100% speed, 0.75 => 75% speed, 1.25 => 125% speed, etc.
1492+ */
1493+ setPlaybackRate: function (rate) {
1494+ if ($("#video").length == 1) {
1495+ $("#video")[0].playbackRate = rate;
1496+ }
1497+ },
1498+ /**
1499+ * Set the volume
1500+ * @param level The volume level from 0 to 100.
1501+ */
1502+ setVideoVolume: function (level) {
1503+ if ($("#video").length == 1) {
1504+ $("#video")[0].volume = level / 100.0;
1505+ }
1506+ },
1507+ /**
1508+ * Mute the volume
1509+ */
1510+ toggleVideoMute: function () {
1511+ if ($("#video").length == 1) {
1512+ $("#video")[0].muted = !$("#video")[0].muted;
1513+ }
1514+ },
1515+ /**
1516+ * Clear the background audio playlist
1517+ */
1518+ clearPlaylist: function () {
1519+ if ($("#background-audio").length == 1) {
1520+ var audio = $("#background-audio")[0];
1521+ /* audio.playList */
1522+ }
1523+ },
1524+ /**
1525+ * Add background audio
1526+ * @param files The list of files as objects in an array
1527+ */
1528+ addBackgroundAudio: function (files) {
1529+ },
1530+ /**
1531+ * Go to a slide.
1532+ * @param slide The slide number or name, e.g. "v1", 0
1533+ */
1534+ goToSlide: function (slide) {
1535+ if (this._slides.hasOwnProperty(slide)) {
1536+ Reveal.slide(this._slides[slide]);
1537+ }
1538+ else {
1539+ Reveal.slide(slide);
1540+ }
1541+ },
1542+ /**
1543+ * Go to the next slide in the list
1544+ */
1545+ next: Reveal.next,
1546+ /**
1547+ * Go to the previous slide in the list
1548+ */
1549+ prev: Reveal.prev,
1550+ /**
1551+ * Blank the screen
1552+ */
1553+ blankToBlack: function () {
1554+ if (!Reveal.isPaused()) {
1555+ Reveal.togglePause();
1556+ }
1557+ // var slidesDiv = $(".slides")[0];
1558+ },
1559+ /**
1560+ * Blank to theme
1561+ */
1562+ blankToTheme: function () {
1563+ var slidesDiv = $(".slides")[0];
1564+ slidesDiv.style.visibility = "hidden";
1565+ var footerDiv = $(".footer")[0];
1566+ footerDiv.style.visibility = "hidden";
1567+ if (Reveal.isPaused()) {
1568+ Reveal.togglePause();
1569+ }
1570+ },
1571+ /**
1572+ * Show the screen
1573+ */
1574+ show: function () {
1575+ var slidesDiv = $(".slides")[0];
1576+ slidesDiv.style.visibility = "visible";
1577+ var footerDiv = $(".footer")[0];
1578+ footerDiv.style.visibility = "visible";
1579+ if (Reveal.isPaused()) {
1580+ Reveal.togglePause();
1581+ }
1582+ },
1583+ /**
1584+ * Figure out how many lines can fit on a slide given the font size
1585+ * @param fontSize The font size in pts
1586+ */
1587+ calculateLineCount: function (fontSize) {
1588+ var p = $(".slides > section > p");
1589+ if (p.length == 0) {
1590+ this.addSlide("v1", "Arky arky");
1591+ p = $(".slides > section > p");
1592+ }
1593+ p = p[0];
1594+ p.style.fontSize = "" + fontSize + "pt";
1595+ var d = $(".slides")[0];
1596+ var lh = parseFloat(_getStyle(p, "line-height"));
1597+ var dh = parseFloat(_getStyle(d, "height"));
1598+ return Math.floor(dh / lh);
1599+ },
1600+ setTheme: function (theme) {
1601+ this._theme = theme;
1602+ var slidesDiv = $(".slides")
1603+ // Set the background
1604+ var globalBackground = $("#global-background")[0];
1605+ var backgroundStyle = {};
1606+ var backgroundHtml = "";
1607+ switch (theme.background_type) {
1608+ case BackgroundType.Transparent:
1609+ backgroundStyle["background"] = "transparent";
1610+ break;
1611+ case BackgroundType.Solid:
1612+ backgroundStyle["background"] = theme.background_color;
1613+ break;
1614+ case BackgroundType.Gradient:
1615+ switch (theme.background_direction) {
1616+ case GradientType.Horizontal:
1617+ backgroundStyle["background"] = _buildLinearGradient("left top", "left bottom",
1618+ theme.background_start_color,
1619+ theme.background_end_color);
1620+ break;
1621+ case GradientType.Vertical:
1622+ backgroundStyle["background"] = _buildLinearGradient("left top", "right top",
1623+ theme.background_start_color,
1624+ theme.background_end_color);
1625+ break;
1626+ case GradientType.LeftTop:
1627+ backgroundStyle["background"] = _buildLinearGradient("left top", "right bottom",
1628+ theme.background_start_color,
1629+ theme.background_end_color);
1630+ break;
1631+ case GradientType.LeftBottom:
1632+ backgroundStyle["background"] = _buildLinearGradient("left bottom", "right top",
1633+ theme.background_start_color,
1634+ theme.background_end_color);
1635+ break;
1636+ case GradientType.Circular:
1637+ backgroundStyle["background"] = _buildRadialGradient(window.innerWidth / 2, theme.background_start_color,
1638+ theme.background_end_color);
1639+ break;
1640+ default:
1641+ backgroundStyle["background"] = "#000";
1642+ }
1643+ break;
1644+ case BackgroundType.Image:
1645+ background_filename = _pathToString(theme.background_filename);
1646+ backgroundStyle["background-image"] = "url('file://" + background_filename + "')";
1647+ break;
1648+ case BackgroundType.Video:
1649+ background_filename = _pathToString(theme.background_filename);
1650+ backgroundStyle["background-color"] = theme.background_border_color;
1651+ backgroundHtml = "<video loop autoplay muted><source src='file://" + background_filename + "'></video>";
1652+ break;
1653+ default:
1654+ backgroundStyle["background"] = "#000";
1655+ }
1656+ globalBackground.style.cssText = "";
1657+ for (var key in backgroundStyle) {
1658+ if (backgroundStyle.hasOwnProperty(key)) {
1659+ globalBackground.style.setProperty(key, backgroundStyle[key]);
1660+ }
1661+ }
1662+ if (!!backgroundHtml) {
1663+ globalBackground.innerHTML = backgroundHtml;
1664+ }
1665+ // set up the main area
1666+ mainStyle = {
1667+ "word-wrap": "break-word",
1668+ /*"margin": "0",
1669+ "padding": "0"*/
1670+ };
1671+ if (!!theme.font_main_outline) {
1672+ mainStyle["-webkit-text-stroke"] = "" + theme.font_main_outline_size + "pt " +
1673+ theme.font_main_outline_color;
1674+ mainStyle["-webkit-text-fill-color"] = theme.font_main_color;
1675+ }
1676+ mainStyle["font-family"] = theme.font_main_name;
1677+ mainStyle["font-size"] = "" + theme.font_main_size + "pt";
1678+ mainStyle["font-style"] = !!theme.font_main_italics ? "italic" : "";
1679+ mainStyle["font-weight"] = !!theme.font_main_bold ? "bold" : "";
1680+ mainStyle["color"] = theme.font_main_color;
1681+ mainStyle["line-height"] = "" + (100 + theme.font_main_line_adjustment) + "%";
1682+ mainStyle["text-align"] = theme.display_horizontal_align;
1683+ if (theme.display_horizontal_align != HorizontalAlign.Justify) {
1684+ mainStyle["white-space"] = "pre-wrap";
1685+ }
1686+ mainStyle["vertical-align"] = theme.display_vertical_align;
1687+ if (theme.hasOwnProperty('font_main_shadow_size')) {
1688+ mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "px " +
1689+ theme.font_main_shadow_size + "px";
1690+ }
1691+ mainStyle["padding-bottom"] = theme.display_vertical_align == VerticalAlign.Bottom ? "0.5em" : "0";
1692+ mainStyle["padding-left"] = !!theme.font_main_outline ? "" + (theme.font_main_outline_size * 2) + "pt" : "0";
1693+ // These need to be fixed, in the Python they use a width passed in as a parameter
1694+ mainStyle["position"] = "absolute";
1695+ mainStyle["width"] = "" + (window.innerWidth - (theme.font_main_outline_size * 4)) + "px";
1696+ mainStyle["height"] = "" + (window.innerHeight - (theme.font_main_outline_size * 4)) + "px";
1697+ mainStyle["left"] = "" + theme.font_main_x + "px";
1698+ mainStyle["top"] = "" + theme.font_main_y + "px";
1699+ var slidesDiv = $(".slides")[0];
1700+ slidesDiv.style.cssText = "";
1701+ for (var key in mainStyle) {
1702+ if (mainStyle.hasOwnProperty(key)) {
1703+ slidesDiv.style.setProperty(key, mainStyle[key]);
1704+ }
1705+ }
1706+ // Set up the footer
1707+ footerStyle = {
1708+ "text-align": "left"
1709+ };
1710+ footerStyle["position"] = "absolute";
1711+ footerStyle["left"] = "" + theme.font_footer_x + "px";
1712+ footerStyle["top"] = "" + theme.font_footer_y + "px";
1713+ footerStyle["bottom"] = "" + (window.innerHeight - theme.font_footer_y - theme.font_footer_height) + "px";
1714+ footerStyle["width"] = "" + theme.font_footer_width + "px";
1715+ footerStyle["font-family"] = theme.font_footer_name;
1716+ footerStyle["font-size"] = "" + theme.font_footer_size + "pt";
1717+ footerStyle["color"] = theme.font_footer_color;
1718+ footerStyle["white-space"] = theme.font_footer_wrap ? "normal" : "nowrap";
1719+ var footer = $(".footer")[0];
1720+ footer.style.cssText = "";
1721+ for (var key in footerStyle) {
1722+ if (footerStyle.hasOwnProperty(key)) {
1723+ footer.style.setProperty(key, footerStyle[key]);
1724+ }
1725+ }
1726+ },
1727+ /**
1728+ * Return the video types supported by the video tag
1729+ */
1730+ getVideoTypes: function () {
1731+ var videoElement = document.createElement('video');
1732+ var videoTypes = [];
1733+ if (videoElement.canPlayType('video/mp4; codecs="mp4v.20.8"') == "probably" ||
1734+ videoElement.canPlayType('video/mp4; codecs="avc1.42E01E"') == "pobably" ||
1735+ videoElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') == "probably") {
1736+ videoTypes.push(['video/mp4', '*.mp4']);
1737+ }
1738+ if (videoElement.canPlayType('video/ogg; codecs="theora"') == "probably") {
1739+ videoTypes.push(['video/ogg', '*.ogv']);
1740+ }
1741+ if (videoElement.canPlayType('video/webm; codecs="vp8, vorbis"') == "probably") {
1742+ videoTypes.push(['video/webm', '*.webm']);
1743+ }
1744+ return videoTypes;
1745+ },
1746+ /**
1747+ * Sets the scale of the page - used to make preview widgets scale
1748+ */
1749+ setScale: function(scale) {
1750+ document.body.style.zoom = scale+"%";
1751+ }
1752+};
1753+new QWebChannel(qt.webChannelTransport, function (channel) {
1754+ window.mediaWatcher = channel.objects.mediaWatcher;
1755+});
1756
1757=== added file 'openlp/core/display/html/openlp-splash-screen.png'
1758Binary files openlp/core/display/html/openlp-splash-screen.png 1970-01-01 00:00:00 +0000 and openlp/core/display/html/openlp-splash-screen.png 2019-02-12 20:56:20 +0000 differ
1759=== added file 'openlp/core/display/html/reveal.css'
1760--- openlp/core/display/html/reveal.css 1970-01-01 00:00:00 +0000
1761+++ openlp/core/display/html/reveal.css 2019-02-12 20:56:20 +0000
1762@@ -0,0 +1,1591 @@
1763+/*!
1764+ * reveal.js
1765+ * http://revealjs.com
1766+ * MIT licensed
1767+ *
1768+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
1769+ */
1770+/*********************************************
1771+ * RESET STYLES
1772+ *********************************************/
1773+html, body, .reveal div, .reveal span, .reveal applet, .reveal object, .reveal iframe,
1774+.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal p, .reveal blockquote, .reveal pre,
1775+.reveal a, .reveal abbr, .reveal acronym, .reveal address, .reveal big, .reveal cite, .reveal code,
1776+.reveal del, .reveal dfn, .reveal em, .reveal img, .reveal ins, .reveal kbd, .reveal q, .reveal s, .reveal samp,
1777+.reveal small, .reveal strike, .reveal strong, .reveal sub, .reveal sup, .reveal tt, .reveal var,
1778+.reveal b, .reveal u, .reveal center,
1779+.reveal dl, .reveal dt, .reveal dd, .reveal ol, .reveal ul, .reveal li,
1780+.reveal fieldset, .reveal form, .reveal label, .reveal legend,
1781+.reveal table, .reveal caption, .reveal tbody, .reveal tfoot, .reveal thead, .reveal tr, .reveal th, .reveal td,
1782+.reveal article, .reveal aside, .reveal canvas, .reveal details, .reveal embed,
1783+.reveal figure, .reveal figcaption, .reveal footer, .reveal header, .reveal hgroup,
1784+.reveal menu, .reveal nav, .reveal output, .reveal ruby, .reveal section, .reveal summary,
1785+.reveal time, .reveal mark, .reveal audio, .reveal video {
1786+ margin: 0;
1787+ padding: 0;
1788+ border: 0;
1789+ font-size: 100%;
1790+ font: inherit;
1791+ vertical-align: baseline; }
1792+
1793+.reveal article, .reveal aside, .reveal details, .reveal figcaption, .reveal figure,
1794+.reveal footer, .reveal header, .reveal hgroup, .reveal menu, .reveal nav, .reveal section {
1795+ display: block; }
1796+
1797+/*********************************************
1798+ * GLOBAL STYLES
1799+ *********************************************/
1800+html,
1801+body {
1802+ width: 100%;
1803+ height: 100%;
1804+ overflow: hidden; }
1805+
1806+body {
1807+ position: relative;
1808+ line-height: 1;
1809+ background-color: #fff;
1810+ color: #000; }
1811+
1812+/*********************************************
1813+ * VIEW FRAGMENTS
1814+ *********************************************/
1815+.reveal .slides section .fragment {
1816+ opacity: 0;
1817+ visibility: hidden;
1818+ transition: all .2s ease; }
1819+ .reveal .slides section .fragment.visible {
1820+ opacity: 1;
1821+ visibility: inherit; }
1822+
1823+.reveal .slides section .fragment.grow {
1824+ opacity: 1;
1825+ visibility: inherit; }
1826+ .reveal .slides section .fragment.grow.visible {
1827+ -webkit-transform: scale(1.3);
1828+ transform: scale(1.3); }
1829+
1830+.reveal .slides section .fragment.shrink {
1831+ opacity: 1;
1832+ visibility: inherit; }
1833+ .reveal .slides section .fragment.shrink.visible {
1834+ -webkit-transform: scale(0.7);
1835+ transform: scale(0.7); }
1836+
1837+.reveal .slides section .fragment.zoom-in {
1838+ -webkit-transform: scale(0.1);
1839+ transform: scale(0.1); }
1840+ .reveal .slides section .fragment.zoom-in.visible {
1841+ -webkit-transform: none;
1842+ transform: none; }
1843+
1844+.reveal .slides section .fragment.fade-out {
1845+ opacity: 1;
1846+ visibility: inherit; }
1847+ .reveal .slides section .fragment.fade-out.visible {
1848+ opacity: 0;
1849+ visibility: hidden; }
1850+
1851+.reveal .slides section .fragment.semi-fade-out {
1852+ opacity: 1;
1853+ visibility: inherit; }
1854+ .reveal .slides section .fragment.semi-fade-out.visible {
1855+ opacity: 0.5;
1856+ visibility: inherit; }
1857+
1858+.reveal .slides section .fragment.strike {
1859+ opacity: 1;
1860+ visibility: inherit; }
1861+ .reveal .slides section .fragment.strike.visible {
1862+ text-decoration: line-through; }
1863+
1864+.reveal .slides section .fragment.fade-up {
1865+ -webkit-transform: translate(0, 20%);
1866+ transform: translate(0, 20%); }
1867+ .reveal .slides section .fragment.fade-up.visible {
1868+ -webkit-transform: translate(0, 0);
1869+ transform: translate(0, 0); }
1870+
1871+.reveal .slides section .fragment.fade-down {
1872+ -webkit-transform: translate(0, -20%);
1873+ transform: translate(0, -20%); }
1874+ .reveal .slides section .fragment.fade-down.visible {
1875+ -webkit-transform: translate(0, 0);
1876+ transform: translate(0, 0); }
1877+
1878+.reveal .slides section .fragment.fade-right {
1879+ -webkit-transform: translate(-20%, 0);
1880+ transform: translate(-20%, 0); }
1881+ .reveal .slides section .fragment.fade-right.visible {
1882+ -webkit-transform: translate(0, 0);
1883+ transform: translate(0, 0); }
1884+
1885+.reveal .slides section .fragment.fade-left {
1886+ -webkit-transform: translate(20%, 0);
1887+ transform: translate(20%, 0); }
1888+ .reveal .slides section .fragment.fade-left.visible {
1889+ -webkit-transform: translate(0, 0);
1890+ transform: translate(0, 0); }
1891+
1892+.reveal .slides section .fragment.fade-in-then-out,
1893+.reveal .slides section .fragment.current-visible {
1894+ opacity: 0;
1895+ visibility: hidden; }
1896+ .reveal .slides section .fragment.fade-in-then-out.current-fragment,
1897+ .reveal .slides section .fragment.current-visible.current-fragment {
1898+ opacity: 1;
1899+ visibility: inherit; }
1900+
1901+.reveal .slides section .fragment.fade-in-then-semi-out {
1902+ opacity: 0;
1903+ visibility: hidden; }
1904+ .reveal .slides section .fragment.fade-in-then-semi-out.visible {
1905+ opacity: 0.5;
1906+ visibility: inherit; }
1907+ .reveal .slides section .fragment.fade-in-then-semi-out.current-fragment {
1908+ opacity: 1;
1909+ visibility: inherit; }
1910+
1911+.reveal .slides section .fragment.highlight-red,
1912+.reveal .slides section .fragment.highlight-current-red,
1913+.reveal .slides section .fragment.highlight-green,
1914+.reveal .slides section .fragment.highlight-current-green,
1915+.reveal .slides section .fragment.highlight-blue,
1916+.reveal .slides section .fragment.highlight-current-blue {
1917+ opacity: 1;
1918+ visibility: inherit; }
1919+
1920+.reveal .slides section .fragment.highlight-red.visible {
1921+ color: #ff2c2d; }
1922+
1923+.reveal .slides section .fragment.highlight-green.visible {
1924+ color: #17ff2e; }
1925+
1926+.reveal .slides section .fragment.highlight-blue.visible {
1927+ color: #1b91ff; }
1928+
1929+.reveal .slides section .fragment.highlight-current-red.current-fragment {
1930+ color: #ff2c2d; }
1931+
1932+.reveal .slides section .fragment.highlight-current-green.current-fragment {
1933+ color: #17ff2e; }
1934+
1935+.reveal .slides section .fragment.highlight-current-blue.current-fragment {
1936+ color: #1b91ff; }
1937+
1938+/*********************************************
1939+ * DEFAULT ELEMENT STYLES
1940+ *********************************************/
1941+/* Fixes issue in Chrome where italic fonts did not appear when printing to PDF */
1942+.reveal:after {
1943+ content: '';
1944+ font-style: italic; }
1945+
1946+.reveal iframe {
1947+ z-index: 1; }
1948+
1949+/** Prevents layering issues in certain browser/transition combinations */
1950+.reveal a {
1951+ position: relative; }
1952+
1953+.reveal .stretch {
1954+ max-width: none;
1955+ max-height: none; }
1956+
1957+.reveal pre.stretch code {
1958+ height: 100%;
1959+ max-height: 100%;
1960+ box-sizing: border-box; }
1961+
1962+/*********************************************
1963+ * CONTROLS
1964+ *********************************************/
1965+@-webkit-keyframes bounce-right {
1966+ 0%, 10%, 25%, 40%, 50% {
1967+ -webkit-transform: translateX(0);
1968+ transform: translateX(0); }
1969+ 20% {
1970+ -webkit-transform: translateX(10px);
1971+ transform: translateX(10px); }
1972+ 30% {
1973+ -webkit-transform: translateX(-5px);
1974+ transform: translateX(-5px); } }
1975+@keyframes bounce-right {
1976+ 0%, 10%, 25%, 40%, 50% {
1977+ -webkit-transform: translateX(0);
1978+ transform: translateX(0); }
1979+ 20% {
1980+ -webkit-transform: translateX(10px);
1981+ transform: translateX(10px); }
1982+ 30% {
1983+ -webkit-transform: translateX(-5px);
1984+ transform: translateX(-5px); } }
1985+
1986+@-webkit-keyframes bounce-down {
1987+ 0%, 10%, 25%, 40%, 50% {
1988+ -webkit-transform: translateY(0);
1989+ transform: translateY(0); }
1990+ 20% {
1991+ -webkit-transform: translateY(10px);
1992+ transform: translateY(10px); }
1993+ 30% {
1994+ -webkit-transform: translateY(-5px);
1995+ transform: translateY(-5px); } }
1996+
1997+@keyframes bounce-down {
1998+ 0%, 10%, 25%, 40%, 50% {
1999+ -webkit-transform: translateY(0);
2000+ transform: translateY(0); }
2001+ 20% {
2002+ -webkit-transform: translateY(10px);
2003+ transform: translateY(10px); }
2004+ 30% {
2005+ -webkit-transform: translateY(-5px);
2006+ transform: translateY(-5px); } }
2007+
2008+.reveal .controls {
2009+ display: none;
2010+ position: absolute;
2011+ top: auto;
2012+ bottom: 12px;
2013+ right: 12px;
2014+ left: auto;
2015+ z-index: 1;
2016+ color: #000;
2017+ pointer-events: none;
2018+ font-size: 10px; }
2019+ .reveal .controls button {
2020+ position: absolute;
2021+ padding: 0;
2022+ background-color: transparent;
2023+ border: 0;
2024+ outline: 0;
2025+ cursor: pointer;
2026+ color: currentColor;
2027+ -webkit-transform: scale(0.9999);
2028+ transform: scale(0.9999);
2029+ transition: color 0.2s ease, opacity 0.2s ease, -webkit-transform 0.2s ease;
2030+ transition: color 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
2031+ z-index: 2;
2032+ pointer-events: auto;
2033+ font-size: inherit;
2034+ visibility: hidden;
2035+ opacity: 0;
2036+ -webkit-appearance: none;
2037+ -webkit-tap-highlight-color: transparent; }
2038+ .reveal .controls .controls-arrow:before,
2039+ .reveal .controls .controls-arrow:after {
2040+ content: '';
2041+ position: absolute;
2042+ top: 0;
2043+ left: 0;
2044+ width: 2.6em;
2045+ height: 0.5em;
2046+ border-radius: 0.25em;
2047+ background-color: currentColor;
2048+ transition: all 0.15s ease, background-color 0.8s ease;
2049+ -webkit-transform-origin: 0.2em 50%;
2050+ transform-origin: 0.2em 50%;
2051+ will-change: transform; }
2052+ .reveal .controls .controls-arrow {
2053+ position: relative;
2054+ width: 3.6em;
2055+ height: 3.6em; }
2056+ .reveal .controls .controls-arrow:before {
2057+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
2058+ transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
2059+ .reveal .controls .controls-arrow:after {
2060+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
2061+ transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
2062+ .reveal .controls .controls-arrow:hover:before {
2063+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(40deg);
2064+ transform: translateX(0.5em) translateY(1.55em) rotate(40deg); }
2065+ .reveal .controls .controls-arrow:hover:after {
2066+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-40deg);
2067+ transform: translateX(0.5em) translateY(1.55em) rotate(-40deg); }
2068+ .reveal .controls .controls-arrow:active:before {
2069+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(36deg);
2070+ transform: translateX(0.5em) translateY(1.55em) rotate(36deg); }
2071+ .reveal .controls .controls-arrow:active:after {
2072+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-36deg);
2073+ transform: translateX(0.5em) translateY(1.55em) rotate(-36deg); }
2074+ .reveal .controls .navigate-left {
2075+ right: 6.4em;
2076+ bottom: 3.2em;
2077+ -webkit-transform: translateX(-10px);
2078+ transform: translateX(-10px); }
2079+ .reveal .controls .navigate-right {
2080+ right: 0;
2081+ bottom: 3.2em;
2082+ -webkit-transform: translateX(10px);
2083+ transform: translateX(10px); }
2084+ .reveal .controls .navigate-right .controls-arrow {
2085+ -webkit-transform: rotate(180deg);
2086+ transform: rotate(180deg); }
2087+ .reveal .controls .navigate-right.highlight {
2088+ -webkit-animation: bounce-right 2s 50 both ease-out;
2089+ animation: bounce-right 2s 50 both ease-out; }
2090+ .reveal .controls .navigate-up {
2091+ right: 3.2em;
2092+ bottom: 6.4em;
2093+ -webkit-transform: translateY(-10px);
2094+ transform: translateY(-10px); }
2095+ .reveal .controls .navigate-up .controls-arrow {
2096+ -webkit-transform: rotate(90deg);
2097+ transform: rotate(90deg); }
2098+ .reveal .controls .navigate-down {
2099+ right: 3.2em;
2100+ bottom: 0;
2101+ -webkit-transform: translateY(10px);
2102+ transform: translateY(10px); }
2103+ .reveal .controls .navigate-down .controls-arrow {
2104+ -webkit-transform: rotate(-90deg);
2105+ transform: rotate(-90deg); }
2106+ .reveal .controls .navigate-down.highlight {
2107+ -webkit-animation: bounce-down 2s 50 both ease-out;
2108+ animation: bounce-down 2s 50 both ease-out; }
2109+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled,
2110+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled {
2111+ opacity: 0.3; }
2112+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled:hover,
2113+ .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled:hover {
2114+ opacity: 1; }
2115+ .reveal .controls[data-controls-back-arrows="hidden"] .navigate-left.enabled,
2116+ .reveal .controls[data-controls-back-arrows="hidden"] .navigate-up.enabled {
2117+ opacity: 0;
2118+ visibility: hidden; }
2119+ .reveal .controls .enabled {
2120+ visibility: visible;
2121+ opacity: 0.9;
2122+ cursor: pointer;
2123+ -webkit-transform: none;
2124+ transform: none; }
2125+ .reveal .controls .enabled.fragmented {
2126+ opacity: 0.5; }
2127+ .reveal .controls .enabled:hover,
2128+ .reveal .controls .enabled.fragmented:hover {
2129+ opacity: 1; }
2130+
2131+.reveal:not(.has-vertical-slides) .controls .navigate-left {
2132+ bottom: 1.4em;
2133+ right: 5.5em; }
2134+
2135+.reveal:not(.has-vertical-slides) .controls .navigate-right {
2136+ bottom: 1.4em;
2137+ right: 0.5em; }
2138+
2139+.reveal:not(.has-horizontal-slides) .controls .navigate-up {
2140+ right: 1.4em;
2141+ bottom: 5em; }
2142+
2143+.reveal:not(.has-horizontal-slides) .controls .navigate-down {
2144+ right: 1.4em;
2145+ bottom: 0.5em; }
2146+
2147+.reveal.has-dark-background .controls {
2148+ color: #fff; }
2149+
2150+.reveal.has-light-background .controls {
2151+ color: #000; }
2152+
2153+.reveal.no-hover .controls .controls-arrow:hover:before,
2154+.reveal.no-hover .controls .controls-arrow:active:before {
2155+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
2156+ transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
2157+
2158+.reveal.no-hover .controls .controls-arrow:hover:after,
2159+.reveal.no-hover .controls .controls-arrow:active:after {
2160+ -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
2161+ transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
2162+
2163+@media screen and (min-width: 500px) {
2164+ .reveal .controls[data-controls-layout="edges"] {
2165+ top: 0;
2166+ right: 0;
2167+ bottom: 0;
2168+ left: 0; }
2169+ .reveal .controls[data-controls-layout="edges"] .navigate-left,
2170+ .reveal .controls[data-controls-layout="edges"] .navigate-right,
2171+ .reveal .controls[data-controls-layout="edges"] .navigate-up,
2172+ .reveal .controls[data-controls-layout="edges"] .navigate-down {
2173+ bottom: auto;
2174+ right: auto; }
2175+ .reveal .controls[data-controls-layout="edges"] .navigate-left {
2176+ top: 50%;
2177+ left: 8px;
2178+ margin-top: -1.8em; }
2179+ .reveal .controls[data-controls-layout="edges"] .navigate-right {
2180+ top: 50%;
2181+ right: 8px;
2182+ margin-top: -1.8em; }
2183+ .reveal .controls[data-controls-layout="edges"] .navigate-up {
2184+ top: 8px;
2185+ left: 50%;
2186+ margin-left: -1.8em; }
2187+ .reveal .controls[data-controls-layout="edges"] .navigate-down {
2188+ bottom: 8px;
2189+ left: 50%;
2190+ margin-left: -1.8em; } }
2191+
2192+/*********************************************
2193+ * PROGRESS BAR
2194+ *********************************************/
2195+.reveal .progress {
2196+ position: absolute;
2197+ display: none;
2198+ height: 3px;
2199+ width: 100%;
2200+ bottom: 0;
2201+ left: 0;
2202+ z-index: 10;
2203+ background-color: rgba(0, 0, 0, 0.2);
2204+ color: #fff; }
2205+
2206+.reveal .progress:after {
2207+ content: '';
2208+ display: block;
2209+ position: absolute;
2210+ height: 10px;
2211+ width: 100%;
2212+ top: -10px; }
2213+
2214+.reveal .progress span {
2215+ display: block;
2216+ height: 100%;
2217+ width: 0px;
2218+ background-color: currentColor;
2219+ transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2220+
2221+/*********************************************
2222+ * SLIDE NUMBER
2223+ *********************************************/
2224+.reveal .slide-number {
2225+ position: absolute;
2226+ display: block;
2227+ right: 8px;
2228+ bottom: 8px;
2229+ z-index: 31;
2230+ font-family: Helvetica, sans-serif;
2231+ font-size: 12px;
2232+ line-height: 1;
2233+ color: #fff;
2234+ background-color: rgba(0, 0, 0, 0.4);
2235+ padding: 5px; }
2236+
2237+.reveal .slide-number a {
2238+ color: currentColor; }
2239+
2240+.reveal .slide-number-delimiter {
2241+ margin: 0 3px; }
2242+
2243+/*********************************************
2244+ * SLIDES
2245+ *********************************************/
2246+.reveal {
2247+ position: relative;
2248+ width: 100%;
2249+ height: 100%;
2250+ overflow: hidden;
2251+ -ms-touch-action: none;
2252+ touch-action: none; }
2253+
2254+@media only screen and (orientation: landscape) {
2255+ .reveal.ua-iphone {
2256+ position: fixed; } }
2257+
2258+.reveal .slides {
2259+ position: absolute;
2260+ width: 100%;
2261+ height: 100%;
2262+ top: 0;
2263+ right: 0;
2264+ bottom: 0;
2265+ left: 0;
2266+ margin: auto;
2267+ pointer-events: none;
2268+ overflow: visible;
2269+ z-index: 1;
2270+ text-align: center;
2271+ -webkit-perspective: 600px;
2272+ perspective: 600px;
2273+ -webkit-perspective-origin: 50% 40%;
2274+ perspective-origin: 50% 40%; }
2275+
2276+.reveal .slides > section {
2277+ -ms-perspective: 600px; }
2278+
2279+.reveal .slides > section,
2280+.reveal .slides > section > section {
2281+ display: none;
2282+ position: absolute;
2283+ width: 100%;
2284+ padding: 20px 0px;
2285+ pointer-events: auto;
2286+ z-index: 10;
2287+ -webkit-transform-style: flat;
2288+ transform-style: flat;
2289+ transition: -webkit-transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), -webkit-transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
2290+ transition: transform-origin 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), transform 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), visibility 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985), opacity 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2291+
2292+/* Global transition speed settings */
2293+.reveal[data-transition-speed="fast"] .slides section {
2294+ transition-duration: 400ms; }
2295+
2296+.reveal[data-transition-speed="slow"] .slides section {
2297+ transition-duration: 1200ms; }
2298+
2299+/* Slide-specific transition speed overrides */
2300+.reveal .slides section[data-transition-speed="fast"] {
2301+ transition-duration: 400ms; }
2302+
2303+.reveal .slides section[data-transition-speed="slow"] {
2304+ transition-duration: 1200ms; }
2305+
2306+.reveal .slides > section.stack {
2307+ padding-top: 0;
2308+ padding-bottom: 0;
2309+ pointer-events: none; }
2310+
2311+.reveal .slides > section.present,
2312+.reveal .slides > section > section.present {
2313+ display: block;
2314+ z-index: 11;
2315+ opacity: 1; }
2316+
2317+.reveal .slides > section:empty,
2318+.reveal .slides > section > section:empty,
2319+.reveal .slides > section[data-background-interactive],
2320+.reveal .slides > section > section[data-background-interactive] {
2321+ pointer-events: none; }
2322+
2323+.reveal.center,
2324+.reveal.center .slides,
2325+.reveal.center .slides section {
2326+ min-height: 0 !important; }
2327+
2328+/* Don't allow interaction with invisible slides */
2329+.reveal .slides > section.future,
2330+.reveal .slides > section > section.future,
2331+.reveal .slides > section.past,
2332+.reveal .slides > section > section.past {
2333+ pointer-events: none; }
2334+
2335+.reveal.overview .slides > section,
2336+.reveal.overview .slides > section > section {
2337+ pointer-events: auto; }
2338+
2339+.reveal .slides > section.past,
2340+.reveal .slides > section.future,
2341+.reveal .slides > section > section.past,
2342+.reveal .slides > section > section.future {
2343+ opacity: 0; }
2344+
2345+/*********************************************
2346+ * Mixins for readability of transitions
2347+ *********************************************/
2348+/*********************************************
2349+ * SLIDE TRANSITION
2350+ * Aliased 'linear' for backwards compatibility
2351+ *********************************************/
2352+.reveal.slide section {
2353+ -webkit-backface-visibility: hidden;
2354+ backface-visibility: hidden; }
2355+
2356+.reveal .slides > section[data-transition=slide].past,
2357+.reveal .slides > section[data-transition~=slide-out].past,
2358+.reveal.slide .slides > section:not([data-transition]).past {
2359+ -webkit-transform: translate(-150%, 0);
2360+ transform: translate(-150%, 0); }
2361+
2362+.reveal .slides > section[data-transition=slide].future,
2363+.reveal .slides > section[data-transition~=slide-in].future,
2364+.reveal.slide .slides > section:not([data-transition]).future {
2365+ -webkit-transform: translate(150%, 0);
2366+ transform: translate(150%, 0); }
2367+
2368+.reveal .slides > section > section[data-transition=slide].past,
2369+.reveal .slides > section > section[data-transition~=slide-out].past,
2370+.reveal.slide .slides > section > section:not([data-transition]).past {
2371+ -webkit-transform: translate(0, -150%);
2372+ transform: translate(0, -150%); }
2373+
2374+.reveal .slides > section > section[data-transition=slide].future,
2375+.reveal .slides > section > section[data-transition~=slide-in].future,
2376+.reveal.slide .slides > section > section:not([data-transition]).future {
2377+ -webkit-transform: translate(0, 150%);
2378+ transform: translate(0, 150%); }
2379+
2380+.reveal.linear section {
2381+ -webkit-backface-visibility: hidden;
2382+ backface-visibility: hidden; }
2383+
2384+.reveal .slides > section[data-transition=linear].past,
2385+.reveal .slides > section[data-transition~=linear-out].past,
2386+.reveal.linear .slides > section:not([data-transition]).past {
2387+ -webkit-transform: translate(-150%, 0);
2388+ transform: translate(-150%, 0); }
2389+
2390+.reveal .slides > section[data-transition=linear].future,
2391+.reveal .slides > section[data-transition~=linear-in].future,
2392+.reveal.linear .slides > section:not([data-transition]).future {
2393+ -webkit-transform: translate(150%, 0);
2394+ transform: translate(150%, 0); }
2395+
2396+.reveal .slides > section > section[data-transition=linear].past,
2397+.reveal .slides > section > section[data-transition~=linear-out].past,
2398+.reveal.linear .slides > section > section:not([data-transition]).past {
2399+ -webkit-transform: translate(0, -150%);
2400+ transform: translate(0, -150%); }
2401+
2402+.reveal .slides > section > section[data-transition=linear].future,
2403+.reveal .slides > section > section[data-transition~=linear-in].future,
2404+.reveal.linear .slides > section > section:not([data-transition]).future {
2405+ -webkit-transform: translate(0, 150%);
2406+ transform: translate(0, 150%); }
2407+
2408+/*********************************************
2409+ * CONVEX TRANSITION
2410+ * Aliased 'default' for backwards compatibility
2411+ *********************************************/
2412+.reveal .slides section[data-transition=default].stack,
2413+.reveal.default .slides section.stack {
2414+ -webkit-transform-style: preserve-3d;
2415+ transform-style: preserve-3d; }
2416+
2417+.reveal .slides > section[data-transition=default].past,
2418+.reveal .slides > section[data-transition~=default-out].past,
2419+.reveal.default .slides > section:not([data-transition]).past {
2420+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2421+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2422+
2423+.reveal .slides > section[data-transition=default].future,
2424+.reveal .slides > section[data-transition~=default-in].future,
2425+.reveal.default .slides > section:not([data-transition]).future {
2426+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2427+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2428+
2429+.reveal .slides > section > section[data-transition=default].past,
2430+.reveal .slides > section > section[data-transition~=default-out].past,
2431+.reveal.default .slides > section > section:not([data-transition]).past {
2432+ -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
2433+ transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
2434+
2435+.reveal .slides > section > section[data-transition=default].future,
2436+.reveal .slides > section > section[data-transition~=default-in].future,
2437+.reveal.default .slides > section > section:not([data-transition]).future {
2438+ -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
2439+ transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
2440+
2441+.reveal .slides section[data-transition=convex].stack,
2442+.reveal.convex .slides section.stack {
2443+ -webkit-transform-style: preserve-3d;
2444+ transform-style: preserve-3d; }
2445+
2446+.reveal .slides > section[data-transition=convex].past,
2447+.reveal .slides > section[data-transition~=convex-out].past,
2448+.reveal.convex .slides > section:not([data-transition]).past {
2449+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2450+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2451+
2452+.reveal .slides > section[data-transition=convex].future,
2453+.reveal .slides > section[data-transition~=convex-in].future,
2454+.reveal.convex .slides > section:not([data-transition]).future {
2455+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2456+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2457+
2458+.reveal .slides > section > section[data-transition=convex].past,
2459+.reveal .slides > section > section[data-transition~=convex-out].past,
2460+.reveal.convex .slides > section > section:not([data-transition]).past {
2461+ -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
2462+ transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0); }
2463+
2464+.reveal .slides > section > section[data-transition=convex].future,
2465+.reveal .slides > section > section[data-transition~=convex-in].future,
2466+.reveal.convex .slides > section > section:not([data-transition]).future {
2467+ -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
2468+ transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0); }
2469+
2470+/*********************************************
2471+ * CONCAVE TRANSITION
2472+ *********************************************/
2473+.reveal .slides section[data-transition=concave].stack,
2474+.reveal.concave .slides section.stack {
2475+ -webkit-transform-style: preserve-3d;
2476+ transform-style: preserve-3d; }
2477+
2478+.reveal .slides > section[data-transition=concave].past,
2479+.reveal .slides > section[data-transition~=concave-out].past,
2480+.reveal.concave .slides > section:not([data-transition]).past {
2481+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
2482+ transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
2483+
2484+.reveal .slides > section[data-transition=concave].future,
2485+.reveal .slides > section[data-transition~=concave-in].future,
2486+.reveal.concave .slides > section:not([data-transition]).future {
2487+ -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
2488+ transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
2489+
2490+.reveal .slides > section > section[data-transition=concave].past,
2491+.reveal .slides > section > section[data-transition~=concave-out].past,
2492+.reveal.concave .slides > section > section:not([data-transition]).past {
2493+ -webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
2494+ transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0); }
2495+
2496+.reveal .slides > section > section[data-transition=concave].future,
2497+.reveal .slides > section > section[data-transition~=concave-in].future,
2498+.reveal.concave .slides > section > section:not([data-transition]).future {
2499+ -webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
2500+ transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0); }
2501+
2502+/*********************************************
2503+ * ZOOM TRANSITION
2504+ *********************************************/
2505+.reveal .slides section[data-transition=zoom],
2506+.reveal.zoom .slides section:not([data-transition]) {
2507+ transition-timing-function: ease; }
2508+
2509+.reveal .slides > section[data-transition=zoom].past,
2510+.reveal .slides > section[data-transition~=zoom-out].past,
2511+.reveal.zoom .slides > section:not([data-transition]).past {
2512+ visibility: hidden;
2513+ -webkit-transform: scale(16);
2514+ transform: scale(16); }
2515+
2516+.reveal .slides > section[data-transition=zoom].future,
2517+.reveal .slides > section[data-transition~=zoom-in].future,
2518+.reveal.zoom .slides > section:not([data-transition]).future {
2519+ visibility: hidden;
2520+ -webkit-transform: scale(0.2);
2521+ transform: scale(0.2); }
2522+
2523+.reveal .slides > section > section[data-transition=zoom].past,
2524+.reveal .slides > section > section[data-transition~=zoom-out].past,
2525+.reveal.zoom .slides > section > section:not([data-transition]).past {
2526+ -webkit-transform: translate(0, -150%);
2527+ transform: translate(0, -150%); }
2528+
2529+.reveal .slides > section > section[data-transition=zoom].future,
2530+.reveal .slides > section > section[data-transition~=zoom-in].future,
2531+.reveal.zoom .slides > section > section:not([data-transition]).future {
2532+ -webkit-transform: translate(0, 150%);
2533+ transform: translate(0, 150%); }
2534+
2535+/*********************************************
2536+ * CUBE TRANSITION
2537+ *
2538+ * WARNING:
2539+ * this is deprecated and will be removed in a
2540+ * future version.
2541+ *********************************************/
2542+.reveal.cube .slides {
2543+ -webkit-perspective: 1300px;
2544+ perspective: 1300px; }
2545+
2546+.reveal.cube .slides section {
2547+ padding: 30px;
2548+ min-height: 700px;
2549+ -webkit-backface-visibility: hidden;
2550+ backface-visibility: hidden;
2551+ box-sizing: border-box;
2552+ -webkit-transform-style: preserve-3d;
2553+ transform-style: preserve-3d; }
2554+
2555+.reveal.center.cube .slides section {
2556+ min-height: 0; }
2557+
2558+.reveal.cube .slides section:not(.stack):before {
2559+ content: '';
2560+ position: absolute;
2561+ display: block;
2562+ width: 100%;
2563+ height: 100%;
2564+ left: 0;
2565+ top: 0;
2566+ background: rgba(0, 0, 0, 0.1);
2567+ border-radius: 4px;
2568+ -webkit-transform: translateZ(-20px);
2569+ transform: translateZ(-20px); }
2570+
2571+.reveal.cube .slides section:not(.stack):after {
2572+ content: '';
2573+ position: absolute;
2574+ display: block;
2575+ width: 90%;
2576+ height: 30px;
2577+ left: 5%;
2578+ bottom: 0;
2579+ background: none;
2580+ z-index: 1;
2581+ border-radius: 4px;
2582+ box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
2583+ -webkit-transform: translateZ(-90px) rotateX(65deg);
2584+ transform: translateZ(-90px) rotateX(65deg); }
2585+
2586+.reveal.cube .slides > section.stack {
2587+ padding: 0;
2588+ background: none; }
2589+
2590+.reveal.cube .slides > section.past {
2591+ -webkit-transform-origin: 100% 0%;
2592+ transform-origin: 100% 0%;
2593+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
2594+ transform: translate3d(-100%, 0, 0) rotateY(-90deg); }
2595+
2596+.reveal.cube .slides > section.future {
2597+ -webkit-transform-origin: 0% 0%;
2598+ transform-origin: 0% 0%;
2599+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
2600+ transform: translate3d(100%, 0, 0) rotateY(90deg); }
2601+
2602+.reveal.cube .slides > section > section.past {
2603+ -webkit-transform-origin: 0% 100%;
2604+ transform-origin: 0% 100%;
2605+ -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
2606+ transform: translate3d(0, -100%, 0) rotateX(90deg); }
2607+
2608+.reveal.cube .slides > section > section.future {
2609+ -webkit-transform-origin: 0% 0%;
2610+ transform-origin: 0% 0%;
2611+ -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
2612+ transform: translate3d(0, 100%, 0) rotateX(-90deg); }
2613+
2614+/*********************************************
2615+ * PAGE TRANSITION
2616+ *
2617+ * WARNING:
2618+ * this is deprecated and will be removed in a
2619+ * future version.
2620+ *********************************************/
2621+.reveal.page .slides {
2622+ -webkit-perspective-origin: 0% 50%;
2623+ perspective-origin: 0% 50%;
2624+ -webkit-perspective: 3000px;
2625+ perspective: 3000px; }
2626+
2627+.reveal.page .slides section {
2628+ padding: 30px;
2629+ min-height: 700px;
2630+ box-sizing: border-box;
2631+ -webkit-transform-style: preserve-3d;
2632+ transform-style: preserve-3d; }
2633+
2634+.reveal.page .slides section.past {
2635+ z-index: 12; }
2636+
2637+.reveal.page .slides section:not(.stack):before {
2638+ content: '';
2639+ position: absolute;
2640+ display: block;
2641+ width: 100%;
2642+ height: 100%;
2643+ left: 0;
2644+ top: 0;
2645+ background: rgba(0, 0, 0, 0.1);
2646+ -webkit-transform: translateZ(-20px);
2647+ transform: translateZ(-20px); }
2648+
2649+.reveal.page .slides section:not(.stack):after {
2650+ content: '';
2651+ position: absolute;
2652+ display: block;
2653+ width: 90%;
2654+ height: 30px;
2655+ left: 5%;
2656+ bottom: 0;
2657+ background: none;
2658+ z-index: 1;
2659+ border-radius: 4px;
2660+ box-shadow: 0px 95px 25px rgba(0, 0, 0, 0.2);
2661+ -webkit-transform: translateZ(-90px) rotateX(65deg); }
2662+
2663+.reveal.page .slides > section.stack {
2664+ padding: 0;
2665+ background: none; }
2666+
2667+.reveal.page .slides > section.past {
2668+ -webkit-transform-origin: 0% 0%;
2669+ transform-origin: 0% 0%;
2670+ -webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
2671+ transform: translate3d(-40%, 0, 0) rotateY(-80deg); }
2672+
2673+.reveal.page .slides > section.future {
2674+ -webkit-transform-origin: 100% 0%;
2675+ transform-origin: 100% 0%;
2676+ -webkit-transform: translate3d(0, 0, 0);
2677+ transform: translate3d(0, 0, 0); }
2678+
2679+.reveal.page .slides > section > section.past {
2680+ -webkit-transform-origin: 0% 0%;
2681+ transform-origin: 0% 0%;
2682+ -webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
2683+ transform: translate3d(0, -40%, 0) rotateX(80deg); }
2684+
2685+.reveal.page .slides > section > section.future {
2686+ -webkit-transform-origin: 0% 100%;
2687+ transform-origin: 0% 100%;
2688+ -webkit-transform: translate3d(0, 0, 0);
2689+ transform: translate3d(0, 0, 0); }
2690+
2691+/*********************************************
2692+ * FADE TRANSITION
2693+ *********************************************/
2694+.reveal .slides section[data-transition=fade],
2695+.reveal.fade .slides section:not([data-transition]),
2696+.reveal.fade .slides > section > section:not([data-transition]) {
2697+ -webkit-transform: none;
2698+ transform: none;
2699+ transition: opacity 0.5s; }
2700+
2701+.reveal.fade.overview .slides section,
2702+.reveal.fade.overview .slides > section > section {
2703+ transition: none; }
2704+
2705+/*********************************************
2706+ * NO TRANSITION
2707+ *********************************************/
2708+.reveal .slides section[data-transition=none],
2709+.reveal.none .slides section:not([data-transition]) {
2710+ -webkit-transform: none;
2711+ transform: none;
2712+ transition: none; }
2713+
2714+/*********************************************
2715+ * PAUSED MODE
2716+ *********************************************/
2717+.reveal .pause-overlay {
2718+ position: absolute;
2719+ top: 0;
2720+ left: 0;
2721+ width: 100%;
2722+ height: 100%;
2723+ background: black;
2724+ visibility: hidden;
2725+ opacity: 0;
2726+ z-index: 100;
2727+ transition: all 1s ease; }
2728+
2729+.reveal .pause-overlay .resume-button {
2730+ position: absolute;
2731+ bottom: 20px;
2732+ right: 20px;
2733+ color: #ccc;
2734+ border-radius: 2px;
2735+ padding: 6px 14px;
2736+ border: 2px solid #ccc;
2737+ font-size: 16px;
2738+ background: transparent;
2739+ cursor: pointer; }
2740+ .reveal .pause-overlay .resume-button:hover {
2741+ color: #fff;
2742+ border-color: #fff; }
2743+
2744+.reveal.paused .pause-overlay {
2745+ visibility: visible;
2746+ opacity: 1; }
2747+
2748+/*********************************************
2749+ * FALLBACK
2750+ *********************************************/
2751+.no-transforms {
2752+ overflow-y: auto; }
2753+
2754+.no-transforms .reveal .slides {
2755+ position: relative;
2756+ width: 80%;
2757+ height: auto !important;
2758+ top: 0;
2759+ left: 50%;
2760+ margin: 0;
2761+ text-align: center; }
2762+
2763+.no-transforms .reveal .controls,
2764+.no-transforms .reveal .progress {
2765+ display: none !important; }
2766+
2767+.no-transforms .reveal .slides section {
2768+ display: block !important;
2769+ opacity: 1 !important;
2770+ position: relative !important;
2771+ height: auto;
2772+ min-height: 0;
2773+ top: 0;
2774+ left: -50%;
2775+ margin: 70px 0;
2776+ -webkit-transform: none;
2777+ transform: none; }
2778+
2779+.no-transforms .reveal .slides section section {
2780+ left: 0; }
2781+
2782+.reveal .no-transition,
2783+.reveal .no-transition * {
2784+ transition: none !important; }
2785+
2786+/*********************************************
2787+ * PER-SLIDE BACKGROUNDS
2788+ *********************************************/
2789+.reveal .backgrounds {
2790+ position: absolute;
2791+ width: 100%;
2792+ height: 100%;
2793+ top: 0;
2794+ left: 0;
2795+ -webkit-perspective: 600px;
2796+ perspective: 600px; }
2797+
2798+.reveal .slide-background {
2799+ display: none;
2800+ position: absolute;
2801+ width: 100%;
2802+ height: 100%;
2803+ opacity: 0;
2804+ visibility: hidden;
2805+ overflow: hidden;
2806+ background-color: transparent;
2807+ transition: all 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
2808+
2809+.reveal .slide-background-content {
2810+ position: absolute;
2811+ width: 100%;
2812+ height: 100%;
2813+ background-position: 50% 50%;
2814+ background-repeat: no-repeat;
2815+ background-size: cover; }
2816+
2817+.reveal .slide-background.stack {
2818+ display: block; }
2819+
2820+.reveal .slide-background.present {
2821+ opacity: 1;
2822+ visibility: visible;
2823+ z-index: 2; }
2824+
2825+.print-pdf .reveal .slide-background {
2826+ opacity: 1 !important;
2827+ visibility: visible !important; }
2828+
2829+/* Video backgrounds */
2830+.reveal .slide-background video {
2831+ position: absolute;
2832+ width: 100%;
2833+ height: 100%;
2834+ max-width: none;
2835+ max-height: none;
2836+ top: 0;
2837+ left: 0;
2838+ -o-object-fit: cover;
2839+ object-fit: cover; }
2840+
2841+.reveal .slide-background[data-background-size="contain"] video {
2842+ -o-object-fit: contain;
2843+ object-fit: contain; }
2844+
2845+/* Immediate transition style */
2846+.reveal[data-background-transition=none] > .backgrounds .slide-background,
2847+.reveal > .backgrounds .slide-background[data-background-transition=none] {
2848+ transition: none; }
2849+
2850+/* Slide */
2851+.reveal[data-background-transition=slide] > .backgrounds .slide-background,
2852+.reveal > .backgrounds .slide-background[data-background-transition=slide] {
2853+ opacity: 1;
2854+ -webkit-backface-visibility: hidden;
2855+ backface-visibility: hidden; }
2856+
2857+.reveal[data-background-transition=slide] > .backgrounds .slide-background.past,
2858+.reveal > .backgrounds .slide-background.past[data-background-transition=slide] {
2859+ -webkit-transform: translate(-100%, 0);
2860+ transform: translate(-100%, 0); }
2861+
2862+.reveal[data-background-transition=slide] > .backgrounds .slide-background.future,
2863+.reveal > .backgrounds .slide-background.future[data-background-transition=slide] {
2864+ -webkit-transform: translate(100%, 0);
2865+ transform: translate(100%, 0); }
2866+
2867+.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.past,
2868+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=slide] {
2869+ -webkit-transform: translate(0, -100%);
2870+ transform: translate(0, -100%); }
2871+
2872+.reveal[data-background-transition=slide] > .backgrounds .slide-background > .slide-background.future,
2873+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=slide] {
2874+ -webkit-transform: translate(0, 100%);
2875+ transform: translate(0, 100%); }
2876+
2877+/* Convex */
2878+.reveal[data-background-transition=convex] > .backgrounds .slide-background.past,
2879+.reveal > .backgrounds .slide-background.past[data-background-transition=convex] {
2880+ opacity: 0;
2881+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
2882+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0); }
2883+
2884+.reveal[data-background-transition=convex] > .backgrounds .slide-background.future,
2885+.reveal > .backgrounds .slide-background.future[data-background-transition=convex] {
2886+ opacity: 0;
2887+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
2888+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0); }
2889+
2890+.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.past,
2891+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=convex] {
2892+ opacity: 0;
2893+ -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0);
2894+ transform: translate3d(0, -100%, 0) rotateX(90deg) translate3d(0, -100%, 0); }
2895+
2896+.reveal[data-background-transition=convex] > .backgrounds .slide-background > .slide-background.future,
2897+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=convex] {
2898+ opacity: 0;
2899+ -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0);
2900+ transform: translate3d(0, 100%, 0) rotateX(-90deg) translate3d(0, 100%, 0); }
2901+
2902+/* Concave */
2903+.reveal[data-background-transition=concave] > .backgrounds .slide-background.past,
2904+.reveal > .backgrounds .slide-background.past[data-background-transition=concave] {
2905+ opacity: 0;
2906+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
2907+ transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0); }
2908+
2909+.reveal[data-background-transition=concave] > .backgrounds .slide-background.future,
2910+.reveal > .backgrounds .slide-background.future[data-background-transition=concave] {
2911+ opacity: 0;
2912+ -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
2913+ transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0); }
2914+
2915+.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.past,
2916+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=concave] {
2917+ opacity: 0;
2918+ -webkit-transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0);
2919+ transform: translate3d(0, -100%, 0) rotateX(-90deg) translate3d(0, -100%, 0); }
2920+
2921+.reveal[data-background-transition=concave] > .backgrounds .slide-background > .slide-background.future,
2922+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=concave] {
2923+ opacity: 0;
2924+ -webkit-transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0);
2925+ transform: translate3d(0, 100%, 0) rotateX(90deg) translate3d(0, 100%, 0); }
2926+
2927+/* Zoom */
2928+.reveal[data-background-transition=zoom] > .backgrounds .slide-background,
2929+.reveal > .backgrounds .slide-background[data-background-transition=zoom] {
2930+ transition-timing-function: ease; }
2931+
2932+.reveal[data-background-transition=zoom] > .backgrounds .slide-background.past,
2933+.reveal > .backgrounds .slide-background.past[data-background-transition=zoom] {
2934+ opacity: 0;
2935+ visibility: hidden;
2936+ -webkit-transform: scale(16);
2937+ transform: scale(16); }
2938+
2939+.reveal[data-background-transition=zoom] > .backgrounds .slide-background.future,
2940+.reveal > .backgrounds .slide-background.future[data-background-transition=zoom] {
2941+ opacity: 0;
2942+ visibility: hidden;
2943+ -webkit-transform: scale(0.2);
2944+ transform: scale(0.2); }
2945+
2946+.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.past,
2947+.reveal > .backgrounds .slide-background > .slide-background.past[data-background-transition=zoom] {
2948+ opacity: 0;
2949+ visibility: hidden;
2950+ -webkit-transform: scale(16);
2951+ transform: scale(16); }
2952+
2953+.reveal[data-background-transition=zoom] > .backgrounds .slide-background > .slide-background.future,
2954+.reveal > .backgrounds .slide-background > .slide-background.future[data-background-transition=zoom] {
2955+ opacity: 0;
2956+ visibility: hidden;
2957+ -webkit-transform: scale(0.2);
2958+ transform: scale(0.2); }
2959+
2960+/* Global transition speed settings */
2961+.reveal[data-transition-speed="fast"] > .backgrounds .slide-background {
2962+ transition-duration: 400ms; }
2963+
2964+.reveal[data-transition-speed="slow"] > .backgrounds .slide-background {
2965+ transition-duration: 1200ms; }
2966+
2967+/*********************************************
2968+ * OVERVIEW
2969+ *********************************************/
2970+.reveal.overview {
2971+ -webkit-perspective-origin: 50% 50%;
2972+ perspective-origin: 50% 50%;
2973+ -webkit-perspective: 700px;
2974+ perspective: 700px; }
2975+ .reveal.overview .slides {
2976+ -moz-transform-style: preserve-3d; }
2977+ .reveal.overview .slides section {
2978+ height: 100%;
2979+ top: 0 !important;
2980+ opacity: 1 !important;
2981+ overflow: hidden;
2982+ visibility: visible !important;
2983+ cursor: pointer;
2984+ box-sizing: border-box; }
2985+ .reveal.overview .slides section:hover,
2986+ .reveal.overview .slides section.present {
2987+ outline: 10px solid rgba(150, 150, 150, 0.4);
2988+ outline-offset: 10px; }
2989+ .reveal.overview .slides section .fragment {
2990+ opacity: 1;
2991+ transition: none; }
2992+ .reveal.overview .slides section:after,
2993+ .reveal.overview .slides section:before {
2994+ display: none !important; }
2995+ .reveal.overview .slides > section.stack {
2996+ padding: 0;
2997+ top: 0 !important;
2998+ background: none;
2999+ outline: none;
3000+ overflow: visible; }
3001+ .reveal.overview .backgrounds {
3002+ -webkit-perspective: inherit;
3003+ perspective: inherit;
3004+ -moz-transform-style: preserve-3d; }
3005+ .reveal.overview .backgrounds .slide-background {
3006+ opacity: 1;
3007+ visibility: visible;
3008+ outline: 10px solid rgba(150, 150, 150, 0.1);
3009+ outline-offset: 10px; }
3010+ .reveal.overview .backgrounds .slide-background.stack {
3011+ overflow: visible; }
3012+
3013+.reveal.overview .slides section,
3014+.reveal.overview-deactivating .slides section {
3015+ transition: none; }
3016+
3017+.reveal.overview .backgrounds .slide-background,
3018+.reveal.overview-deactivating .backgrounds .slide-background {
3019+ transition: none; }
3020+
3021+/*********************************************
3022+ * RTL SUPPORT
3023+ *********************************************/
3024+.reveal.rtl .slides,
3025+.reveal.rtl .slides h1,
3026+.reveal.rtl .slides h2,
3027+.reveal.rtl .slides h3,
3028+.reveal.rtl .slides h4,
3029+.reveal.rtl .slides h5,
3030+.reveal.rtl .slides h6 {
3031+ direction: rtl;
3032+ font-family: sans-serif; }
3033+
3034+.reveal.rtl pre,
3035+.reveal.rtl code {
3036+ direction: ltr; }
3037+
3038+.reveal.rtl ol,
3039+.reveal.rtl ul {
3040+ text-align: right; }
3041+
3042+.reveal.rtl .progress span {
3043+ float: right; }
3044+
3045+/*********************************************
3046+ * PARALLAX BACKGROUND
3047+ *********************************************/
3048+.reveal.has-parallax-background .backgrounds {
3049+ transition: all 0.8s ease; }
3050+
3051+/* Global transition speed settings */
3052+.reveal.has-parallax-background[data-transition-speed="fast"] .backgrounds {
3053+ transition-duration: 400ms; }
3054+
3055+.reveal.has-parallax-background[data-transition-speed="slow"] .backgrounds {
3056+ transition-duration: 1200ms; }
3057+
3058+/*********************************************
3059+ * LINK PREVIEW OVERLAY
3060+ *********************************************/
3061+.reveal .overlay {
3062+ position: absolute;
3063+ top: 0;
3064+ left: 0;
3065+ width: 100%;
3066+ height: 100%;
3067+ z-index: 1000;
3068+ background: rgba(0, 0, 0, 0.9);
3069+ opacity: 0;
3070+ visibility: hidden;
3071+ transition: all 0.3s ease; }
3072+
3073+.reveal .overlay.visible {
3074+ opacity: 1;
3075+ visibility: visible; }
3076+
3077+.reveal .overlay .spinner {
3078+ position: absolute;
3079+ display: block;
3080+ top: 50%;
3081+ left: 50%;
3082+ width: 32px;
3083+ height: 32px;
3084+ margin: -16px 0 0 -16px;
3085+ z-index: 10;
3086+ background-image: url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);
3087+ visibility: visible;
3088+ opacity: 0.6;
3089+ transition: all 0.3s ease; }
3090+
3091+.reveal .overlay header {
3092+ position: absolute;
3093+ left: 0;
3094+ top: 0;
3095+ width: 100%;
3096+ height: 40px;
3097+ z-index: 2;
3098+ border-bottom: 1px solid #222; }
3099+
3100+.reveal .overlay header a {
3101+ display: inline-block;
3102+ width: 40px;
3103+ height: 40px;
3104+ line-height: 36px;
3105+ padding: 0 10px;
3106+ float: right;
3107+ opacity: 0.6;
3108+ box-sizing: border-box; }
3109+
3110+.reveal .overlay header a:hover {
3111+ opacity: 1; }
3112+
3113+.reveal .overlay header a .icon {
3114+ display: inline-block;
3115+ width: 20px;
3116+ height: 20px;
3117+ background-position: 50% 50%;
3118+ background-size: 100%;
3119+ background-repeat: no-repeat; }
3120+
3121+.reveal .overlay header a.close .icon {
3122+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC); }
3123+
3124+.reveal .overlay header a.external .icon {
3125+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==); }
3126+
3127+.reveal .overlay .viewport {
3128+ position: absolute;
3129+ display: -webkit-box;
3130+ display: -ms-flexbox;
3131+ display: flex;
3132+ top: 40px;
3133+ right: 0;
3134+ bottom: 0;
3135+ left: 0; }
3136+
3137+.reveal .overlay.overlay-preview .viewport iframe {
3138+ width: 100%;
3139+ height: 100%;
3140+ max-width: 100%;
3141+ max-height: 100%;
3142+ border: 0;
3143+ opacity: 0;
3144+ visibility: hidden;
3145+ transition: all 0.3s ease; }
3146+
3147+.reveal .overlay.overlay-preview.loaded .viewport iframe {
3148+ opacity: 1;
3149+ visibility: visible; }
3150+
3151+.reveal .overlay.overlay-preview.loaded .viewport-inner {
3152+ position: absolute;
3153+ z-index: -1;
3154+ left: 0;
3155+ top: 45%;
3156+ width: 100%;
3157+ text-align: center;
3158+ letter-spacing: normal; }
3159+
3160+.reveal .overlay.overlay-preview .x-frame-error {
3161+ opacity: 0;
3162+ transition: opacity 0.3s ease 0.3s; }
3163+
3164+.reveal .overlay.overlay-preview.loaded .x-frame-error {
3165+ opacity: 1; }
3166+
3167+.reveal .overlay.overlay-preview.loaded .spinner {
3168+ opacity: 0;
3169+ visibility: hidden;
3170+ -webkit-transform: scale(0.2);
3171+ transform: scale(0.2); }
3172+
3173+.reveal .overlay.overlay-help .viewport {
3174+ overflow: auto;
3175+ color: #fff; }
3176+
3177+.reveal .overlay.overlay-help .viewport .viewport-inner {
3178+ width: 600px;
3179+ margin: auto;
3180+ padding: 20px 20px 80px 20px;
3181+ text-align: center;
3182+ letter-spacing: normal; }
3183+
3184+.reveal .overlay.overlay-help .viewport .viewport-inner .title {
3185+ font-size: 20px; }
3186+
3187+.reveal .overlay.overlay-help .viewport .viewport-inner table {
3188+ border: 1px solid #fff;
3189+ border-collapse: collapse;
3190+ font-size: 16px; }
3191+
3192+.reveal .overlay.overlay-help .viewport .viewport-inner table th,
3193+.reveal .overlay.overlay-help .viewport .viewport-inner table td {
3194+ width: 200px;
3195+ padding: 14px;
3196+ border: 1px solid #fff;
3197+ vertical-align: middle; }
3198+
3199+.reveal .overlay.overlay-help .viewport .viewport-inner table th {
3200+ padding-top: 20px;
3201+ padding-bottom: 20px; }
3202+
3203+/*********************************************
3204+ * PLAYBACK COMPONENT
3205+ *********************************************/
3206+.reveal .playback {
3207+ position: absolute;
3208+ left: 15px;
3209+ bottom: 20px;
3210+ z-index: 30;
3211+ cursor: pointer;
3212+ transition: all 400ms ease;
3213+ -webkit-tap-highlight-color: transparent; }
3214+
3215+.reveal.overview .playback {
3216+ opacity: 0;
3217+ visibility: hidden; }
3218+
3219+/*********************************************
3220+ * ROLLING LINKS
3221+ *********************************************/
3222+.reveal .roll {
3223+ display: inline-block;
3224+ line-height: 1.2;
3225+ overflow: hidden;
3226+ vertical-align: top;
3227+ -webkit-perspective: 400px;
3228+ perspective: 400px;
3229+ -webkit-perspective-origin: 50% 50%;
3230+ perspective-origin: 50% 50%; }
3231+
3232+.reveal .roll:hover {
3233+ background: none;
3234+ text-shadow: none; }
3235+
3236+.reveal .roll span {
3237+ display: block;
3238+ position: relative;
3239+ padding: 0 2px;
3240+ pointer-events: none;
3241+ transition: all 400ms ease;
3242+ -webkit-transform-origin: 50% 0%;
3243+ transform-origin: 50% 0%;
3244+ -webkit-transform-style: preserve-3d;
3245+ transform-style: preserve-3d;
3246+ -webkit-backface-visibility: hidden;
3247+ backface-visibility: hidden; }
3248+
3249+.reveal .roll:hover span {
3250+ background: rgba(0, 0, 0, 0.5);
3251+ -webkit-transform: translate3d(0px, 0px, -45px) rotateX(90deg);
3252+ transform: translate3d(0px, 0px, -45px) rotateX(90deg); }
3253+
3254+.reveal .roll span:after {
3255+ content: attr(data-title);
3256+ display: block;
3257+ position: absolute;
3258+ left: 0;
3259+ top: 0;
3260+ padding: 0 2px;
3261+ -webkit-backface-visibility: hidden;
3262+ backface-visibility: hidden;
3263+ -webkit-transform-origin: 50% 0%;
3264+ transform-origin: 50% 0%;
3265+ -webkit-transform: translate3d(0px, 110%, 0px) rotateX(-90deg);
3266+ transform: translate3d(0px, 110%, 0px) rotateX(-90deg); }
3267+
3268+/*********************************************
3269+ * SPEAKER NOTES
3270+ *********************************************/
3271+.reveal aside.notes {
3272+ display: none; }
3273+
3274+.reveal .speaker-notes {
3275+ display: none;
3276+ position: absolute;
3277+ width: 25vw;
3278+ height: 100%;
3279+ top: 0;
3280+ left: 100%;
3281+ padding: 14px 18px 14px 18px;
3282+ z-index: 1;
3283+ font-size: 18px;
3284+ line-height: 1.4;
3285+ border: 1px solid rgba(0, 0, 0, 0.05);
3286+ color: #222;
3287+ background-color: #f5f5f5;
3288+ overflow: auto;
3289+ box-sizing: border-box;
3290+ text-align: left;
3291+ font-family: Helvetica, sans-serif;
3292+ -webkit-overflow-scrolling: touch; }
3293+ .reveal .speaker-notes .notes-placeholder {
3294+ color: #ccc;
3295+ font-style: italic; }
3296+ .reveal .speaker-notes:focus {
3297+ outline: none; }
3298+ .reveal .speaker-notes:before {
3299+ content: 'Speaker notes';
3300+ display: block;
3301+ margin-bottom: 10px;
3302+ opacity: 0.5; }
3303+
3304+.reveal.show-notes {
3305+ max-width: 75vw;
3306+ overflow: visible; }
3307+
3308+.reveal.show-notes .speaker-notes {
3309+ display: block; }
3310+
3311+@media screen and (min-width: 1600px) {
3312+ .reveal .speaker-notes {
3313+ font-size: 20px; } }
3314+
3315+@media screen and (max-width: 1024px) {
3316+ .reveal.show-notes {
3317+ border-left: 0;
3318+ max-width: none;
3319+ max-height: 70%;
3320+ overflow: visible; }
3321+ .reveal.show-notes .speaker-notes {
3322+ top: 100%;
3323+ left: 0;
3324+ width: 100%;
3325+ height: 42.8571428571%; } }
3326+
3327+@media screen and (max-width: 600px) {
3328+ .reveal.show-notes {
3329+ max-height: 60%; }
3330+ .reveal.show-notes .speaker-notes {
3331+ top: 100%;
3332+ height: 66.6666666667%; }
3333+ .reveal .speaker-notes {
3334+ font-size: 14px; } }
3335+
3336+/*********************************************
3337+ * ZOOM PLUGIN
3338+ *********************************************/
3339+.zoomed .reveal *,
3340+.zoomed .reveal *:before,
3341+.zoomed .reveal *:after {
3342+ -webkit-backface-visibility: visible !important;
3343+ backface-visibility: visible !important; }
3344+
3345+.zoomed .reveal .progress,
3346+.zoomed .reveal .controls {
3347+ opacity: 0; }
3348+
3349+.zoomed .reveal .roll span {
3350+ background: none; }
3351+
3352+.zoomed .reveal .roll span:after {
3353+ visibility: hidden; }
3354
3355=== added file 'openlp/core/display/html/reveal.js'
3356--- openlp/core/display/html/reveal.js 1970-01-01 00:00:00 +0000
3357+++ openlp/core/display/html/reveal.js 2019-02-12 20:56:20 +0000
3358@@ -0,0 +1,5586 @@
3359+/*!
3360+ * reveal.js
3361+ * http://revealjs.com
3362+ * MIT licensed
3363+ *
3364+ * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
3365+ */
3366+(function( root, factory ) {
3367+ if( typeof define === 'function' && define.amd ) {
3368+ // AMD. Register as an anonymous module.
3369+ define( function() {
3370+ root.Reveal = factory();
3371+ return root.Reveal;
3372+ } );
3373+ } else if( typeof exports === 'object' ) {
3374+ // Node. Does not work with strict CommonJS.
3375+ module.exports = factory();
3376+ } else {
3377+ // Browser globals.
3378+ root.Reveal = factory();
3379+ }
3380+}( this, function() {
3381+
3382+ 'use strict';
3383+
3384+ var Reveal;
3385+
3386+ // The reveal.js version
3387+ var VERSION = '3.7.0';
3388+
3389+ var SLIDES_SELECTOR = '.slides section',
3390+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
3391+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
3392+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
3393+ UA = navigator.userAgent,
3394+
3395+ // Configuration defaults, can be overridden at initialization time
3396+ config = {
3397+
3398+ // The "normal" size of the presentation, aspect ratio will be preserved
3399+ // when the presentation is scaled to fit different resolutions
3400+ width: 960,
3401+ height: 700,
3402+
3403+ // Factor of the display size that should remain empty around the content
3404+ margin: 0.04,
3405+
3406+ // Bounds for smallest/largest possible scale to apply to content
3407+ minScale: 0.2,
3408+ maxScale: 2.0,
3409+
3410+ // Display presentation control arrows
3411+ controls: true,
3412+
3413+ // Help the user learn the controls by providing hints, for example by
3414+ // bouncing the down arrow when they first encounter a vertical slide
3415+ controlsTutorial: true,
3416+
3417+ // Determines where controls appear, "edges" or "bottom-right"
3418+ controlsLayout: 'bottom-right',
3419+
3420+ // Visibility rule for backwards navigation arrows; "faded", "hidden"
3421+ // or "visible"
3422+ controlsBackArrows: 'faded',
3423+
3424+ // Display a presentation progress bar
3425+ progress: true,
3426+
3427+ // Display the page number of the current slide
3428+ slideNumber: false,
3429+
3430+ // Use 1 based indexing for # links to match slide number (default is zero
3431+ // based)
3432+ hashOneBasedIndex: false,
3433+
3434+ // Determine which displays to show the slide number on
3435+ showSlideNumber: 'all',
3436+
3437+ // Push each slide change to the browser history
3438+ history: false,
3439+
3440+ // Enable keyboard shortcuts for navigation
3441+ keyboard: true,
3442+
3443+ // Optional function that blocks keyboard events when retuning false
3444+ keyboardCondition: null,
3445+
3446+ // Enable the slide overview mode
3447+ overview: true,
3448+
3449+ // Disables the default reveal.js slide layout so that you can use
3450+ // custom CSS layout
3451+ disableLayout: false,
3452+
3453+ // Vertical centering of slides
3454+ center: true,
3455+
3456+ // Enables touch navigation on devices with touch input
3457+ touch: true,
3458+
3459+ // Loop the presentation
3460+ loop: false,
3461+
3462+ // Change the presentation direction to be RTL
3463+ rtl: false,
3464+
3465+ // Randomizes the order of slides each time the presentation loads
3466+ shuffle: false,
3467+
3468+ // Turns fragments on and off globally
3469+ fragments: true,
3470+
3471+ // Flags whether to include the current fragment in the URL,
3472+ // so that reloading brings you to the same fragment position
3473+ fragmentInURL: false,
3474+
3475+ // Flags if the presentation is running in an embedded mode,
3476+ // i.e. contained within a limited portion of the screen
3477+ embedded: false,
3478+
3479+ // Flags if we should show a help overlay when the question-mark
3480+ // key is pressed
3481+ help: true,
3482+
3483+ // Flags if it should be possible to pause the presentation (blackout)
3484+ pause: true,
3485+
3486+ // Flags if speaker notes should be visible to all viewers
3487+ showNotes: false,
3488+
3489+ // Global override for autolaying embedded media (video/audio/iframe)
3490+ // - null: Media will only autoplay if data-autoplay is present
3491+ // - true: All media will autoplay, regardless of individual setting
3492+ // - false: No media will autoplay, regardless of individual setting
3493+ autoPlayMedia: null,
3494+
3495+ // Controls automatic progression to the next slide
3496+ // - 0: Auto-sliding only happens if the data-autoslide HTML attribute
3497+ // is present on the current slide or fragment
3498+ // - 1+: All slides will progress automatically at the given interval
3499+ // - false: No auto-sliding, even if data-autoslide is present
3500+ autoSlide: 0,
3501+
3502+ // Stop auto-sliding after user input
3503+ autoSlideStoppable: true,
3504+
3505+ // Use this method for navigation when auto-sliding (defaults to navigateNext)
3506+ autoSlideMethod: null,
3507+
3508+ // Specify the average time in seconds that you think you will spend
3509+ // presenting each slide. This is used to show a pacing timer in the
3510+ // speaker view
3511+ defaultTiming: null,
3512+
3513+ // Enable slide navigation via mouse wheel
3514+ mouseWheel: false,
3515+
3516+ // Apply a 3D roll to links on hover
3517+ rollingLinks: false,
3518+
3519+ // Hides the address bar on mobile devices
3520+ hideAddressBar: true,
3521+
3522+ // Opens links in an iframe preview overlay
3523+ // Add `data-preview-link` and `data-preview-link="false"` to customise each link
3524+ // individually
3525+ previewLinks: false,
3526+
3527+ // Exposes the reveal.js API through window.postMessage
3528+ postMessage: true,
3529+
3530+ // Dispatches all reveal.js events to the parent window through postMessage
3531+ postMessageEvents: false,
3532+
3533+ // Focuses body when page changes visibility to ensure keyboard shortcuts work
3534+ focusBodyOnPageVisibilityChange: true,
3535+
3536+ // Transition style
3537+ transition: 'slide', // none/fade/slide/convex/concave/zoom
3538+
3539+ // Transition speed
3540+ transitionSpeed: 'default', // default/fast/slow
3541+
3542+ // Transition style for full page slide backgrounds
3543+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
3544+
3545+ // Parallax background image
3546+ parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
3547+
3548+ // Parallax background size
3549+ parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
3550+
3551+ // Parallax background repeat
3552+ parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
3553+
3554+ // Parallax background position
3555+ parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
3556+
3557+ // Amount of pixels to move the parallax background per slide step
3558+ parallaxBackgroundHorizontal: null,
3559+ parallaxBackgroundVertical: null,
3560+
3561+ // The maximum number of pages a single slide can expand onto when printing
3562+ // to PDF, unlimited by default
3563+ pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
3564+
3565+ // Prints each fragment on a separate slide
3566+ pdfSeparateFragments: true,
3567+
3568+ // Offset used to reduce the height of content within exported PDF pages.
3569+ // This exists to account for environment differences based on how you
3570+ // print to PDF. CLI printing options, like phantomjs and wkpdf, can end
3571+ // on precisely the total height of the document whereas in-browser
3572+ // printing has to end one pixel before.
3573+ pdfPageHeightOffset: -1,
3574+
3575+ // Number of slides away from the current that are visible
3576+ viewDistance: 3,
3577+
3578+ // The display mode that will be used to show slides
3579+ display: 'block',
3580+
3581+ // Script dependencies to load
3582+ dependencies: []
3583+
3584+ },
3585+
3586+ // Flags if Reveal.initialize() has been called
3587+ initialized = false,
3588+
3589+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
3590+ loaded = false,
3591+
3592+ // Flags if the overview mode is currently active
3593+ overview = false,
3594+
3595+ // Holds the dimensions of our overview slides, including margins
3596+ overviewSlideWidth = null,
3597+ overviewSlideHeight = null,
3598+
3599+ // The horizontal and vertical index of the currently active slide
3600+ indexh,
3601+ indexv,
3602+
3603+ // The previous and current slide HTML elements
3604+ previousSlide,
3605+ currentSlide,
3606+
3607+ previousBackground,
3608+
3609+ // Remember which directions that the user has navigated towards
3610+ hasNavigatedRight = false,
3611+ hasNavigatedDown = false,
3612+
3613+ // Slides may hold a data-state attribute which we pick up and apply
3614+ // as a class to the body. This list contains the combined state of
3615+ // all current slides.
3616+ state = [],
3617+
3618+ // The current scale of the presentation (see width/height config)
3619+ scale = 1,
3620+
3621+ // CSS transform that is currently applied to the slides container,
3622+ // split into two groups
3623+ slidesTransform = { layout: '', overview: '' },
3624+
3625+ // Cached references to DOM elements
3626+ dom = {},
3627+
3628+ // Features supported by the browser, see #checkCapabilities()
3629+ features = {},
3630+
3631+ // Client is a mobile device, see #checkCapabilities()
3632+ isMobileDevice,
3633+
3634+ // Client is a desktop Chrome, see #checkCapabilities()
3635+ isChrome,
3636+
3637+ // Throttles mouse wheel navigation
3638+ lastMouseWheelStep = 0,
3639+
3640+ // Delays updates to the URL due to a Chrome thumbnailer bug
3641+ writeURLTimeout = 0,
3642+
3643+ // Flags if the interaction event listeners are bound
3644+ eventsAreBound = false,
3645+
3646+ // The current auto-slide duration
3647+ autoSlide = 0,
3648+
3649+ // Auto slide properties
3650+ autoSlidePlayer,
3651+ autoSlideTimeout = 0,
3652+ autoSlideStartTime = -1,
3653+ autoSlidePaused = false,
3654+
3655+ // Holds information about the currently ongoing touch input
3656+ touch = {
3657+ startX: 0,
3658+ startY: 0,
3659+ startSpan: 0,
3660+ startCount: 0,
3661+ captured: false,
3662+ threshold: 40
3663+ },
3664+
3665+ // Holds information about the keyboard shortcuts
3666+ keyboardShortcuts = {
3667+ 'N , SPACE': 'Next slide',
3668+ 'P': 'Previous slide',
3669+ '&#8592; , H': 'Navigate left',
3670+ '&#8594; , L': 'Navigate right',
3671+ '&#8593; , K': 'Navigate up',
3672+ '&#8595; , J': 'Navigate down',
3673+ 'Home': 'First slide',
3674+ 'End': 'Last slide',
3675+ 'B , .': 'Pause',
3676+ 'F': 'Fullscreen',
3677+ 'ESC, O': 'Slide overview'
3678+ },
3679+
3680+ // Holds custom key code mappings
3681+ registeredKeyBindings = {};
3682+
3683+ /**
3684+ * Starts up the presentation if the client is capable.
3685+ */
3686+ function initialize( options ) {
3687+
3688+ // Make sure we only initialize once
3689+ if( initialized === true ) return;
3690+
3691+ initialized = true;
3692+
3693+ checkCapabilities();
3694+
3695+ if( !features.transforms2d && !features.transforms3d ) {
3696+ document.body.setAttribute( 'class', 'no-transforms' );
3697+
3698+ // Since JS won't be running any further, we load all lazy
3699+ // loading elements upfront
3700+ var images = toArray( document.getElementsByTagName( 'img' ) ),
3701+ iframes = toArray( document.getElementsByTagName( 'iframe' ) );
3702+
3703+ var lazyLoadable = images.concat( iframes );
3704+
3705+ for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
3706+ var element = lazyLoadable[i];
3707+ if( element.getAttribute( 'data-src' ) ) {
3708+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
3709+ element.removeAttribute( 'data-src' );
3710+ }
3711+ }
3712+
3713+ // If the browser doesn't support core features we won't be
3714+ // using JavaScript to control the presentation
3715+ return;
3716+ }
3717+
3718+ // Cache references to key DOM elements
3719+ dom.wrapper = document.querySelector( '.reveal' );
3720+ dom.slides = document.querySelector( '.reveal .slides' );
3721+
3722+ // Force a layout when the whole page, incl fonts, has loaded
3723+ window.addEventListener( 'load', layout, false );
3724+
3725+ var query = Reveal.getQueryHash();
3726+
3727+ // Do not accept new dependencies via query config to avoid
3728+ // the potential of malicious script injection
3729+ if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
3730+
3731+ // Copy options over to our config object
3732+ extend( config, options );
3733+ extend( config, query );
3734+
3735+ // Hide the address bar in mobile browsers
3736+ hideAddressBar();
3737+
3738+ // Loads the dependencies and continues to #start() once done
3739+ load();
3740+
3741+ }
3742+
3743+ /**
3744+ * Restarts up the presentation if the client is capable.
3745+ */
3746+ function reinitialize() {
3747+ initialized = false;
3748+ initialize(config);
3749+ }
3750+
3751+ /**
3752+ * Inspect the client to see what it's capable of, this
3753+ * should only happens once per runtime.
3754+ */
3755+ function checkCapabilities() {
3756+
3757+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );
3758+ isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
3759+
3760+ var testElement = document.createElement( 'div' );
3761+
3762+ features.transforms3d = 'WebkitPerspective' in testElement.style ||
3763+ 'MozPerspective' in testElement.style ||
3764+ 'msPerspective' in testElement.style ||
3765+ 'OPerspective' in testElement.style ||
3766+ 'perspective' in testElement.style;
3767+
3768+ features.transforms2d = 'WebkitTransform' in testElement.style ||
3769+ 'MozTransform' in testElement.style ||
3770+ 'msTransform' in testElement.style ||
3771+ 'OTransform' in testElement.style ||
3772+ 'transform' in testElement.style;
3773+
3774+ features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
3775+ features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
3776+
3777+ features.canvas = !!document.createElement( 'canvas' ).getContext;
3778+
3779+ // Transitions in the overview are disabled in desktop and
3780+ // Safari due to lag
3781+ features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( UA );
3782+
3783+ // Flags if we should use zoom instead of transform to scale
3784+ // up slides. Zoom produces crisper results but has a lot of
3785+ // xbrowser quirks so we only use it in whitelsited browsers.
3786+ features.zoom = 'zoom' in testElement.style && !isMobileDevice &&
3787+ ( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );
3788+
3789+ }
3790+
3791+ /**
3792+ * Loads the dependencies of reveal.js. Dependencies are
3793+ * defined via the configuration option 'dependencies'
3794+ * and will be loaded prior to starting/binding reveal.js.
3795+ * Some dependencies may have an 'async' flag, if so they
3796+ * will load after reveal.js has been started up.
3797+ */
3798+ function load() {
3799+
3800+ var scripts = [],
3801+ scriptsAsync = [],
3802+ scriptsToPreload = 0;
3803+
3804+ // Called once synchronous scripts finish loading
3805+ function proceed() {
3806+ if( scriptsAsync.length ) {
3807+ // Load asynchronous scripts
3808+ head.js.apply( null, scriptsAsync );
3809+ }
3810+
3811+ start();
3812+ }
3813+
3814+ function loadScript( s ) {
3815+ head.ready( s.src.match( /([\w\d_\-]*)\.?js(\?[\w\d.=&]*)?$|[^\\\/]*$/i )[0], function() {
3816+ // Extension may contain callback functions
3817+ if( typeof s.callback === 'function' ) {
3818+ s.callback.apply( this );
3819+ }
3820+
3821+ if( --scriptsToPreload === 0 ) {
3822+ proceed();
3823+ }
3824+ });
3825+ }
3826+
3827+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
3828+ var s = config.dependencies[i];
3829+
3830+ // Load if there's no condition or the condition is truthy
3831+ if( !s.condition || s.condition() ) {
3832+ if( s.async ) {
3833+ scriptsAsync.push( s.src );
3834+ }
3835+ else {
3836+ scripts.push( s.src );
3837+ }
3838+
3839+ loadScript( s );
3840+ }
3841+ }
3842+
3843+ if( scripts.length ) {
3844+ scriptsToPreload = scripts.length;
3845+
3846+ // Load synchronous scripts
3847+ head.js.apply( null, scripts );
3848+ }
3849+ else {
3850+ proceed();
3851+ }
3852+
3853+ }
3854+
3855+ /**
3856+ * Starts up reveal.js by binding input events and navigating
3857+ * to the current URL deeplink if there is one.
3858+ */
3859+ function start() {
3860+
3861+ loaded = true;
3862+
3863+ // Make sure we've got all the DOM elements we need
3864+ setupDOM();
3865+
3866+ // Listen to messages posted to this window
3867+ setupPostMessage();
3868+
3869+ // Prevent the slides from being scrolled out of view
3870+ setupScrollPrevention();
3871+
3872+ // Resets all vertical slides so that only the first is visible
3873+ resetVerticalSlides();
3874+
3875+ // Updates the presentation to match the current configuration values
3876+ configure();
3877+
3878+ // Read the initial hash
3879+ readURL();
3880+
3881+ // Update all backgrounds
3882+ updateBackground( true );
3883+
3884+ // Notify listeners that the presentation is ready but use a 1ms
3885+ // timeout to ensure it's not fired synchronously after #initialize()
3886+ setTimeout( function() {
3887+ // Enable transitions now that we're loaded
3888+ dom.slides.classList.remove( 'no-transition' );
3889+
3890+ dom.wrapper.classList.add( 'ready' );
3891+
3892+ dispatchEvent( 'ready', {
3893+ 'indexh': indexh,
3894+ 'indexv': indexv,
3895+ 'currentSlide': currentSlide
3896+ } );
3897+ }, 1 );
3898+
3899+ // Special setup and config is required when printing to PDF
3900+ if( isPrintingPDF() ) {
3901+ removeEventListeners();
3902+
3903+ // The document needs to have loaded for the PDF layout
3904+ // measurements to be accurate
3905+ if( document.readyState === 'complete' ) {
3906+ setupPDF();
3907+ }
3908+ else {
3909+ window.addEventListener( 'load', setupPDF );
3910+ }
3911+ }
3912+
3913+ }
3914+
3915+ /**
3916+ * Finds and stores references to DOM elements which are
3917+ * required by the presentation. If a required element is
3918+ * not found, it is created.
3919+ */
3920+ function setupDOM() {
3921+
3922+ // Prevent transitions while we're loading
3923+ dom.slides.classList.add( 'no-transition' );
3924+
3925+ if( isMobileDevice ) {
3926+ dom.wrapper.classList.add( 'no-hover' );
3927+ }
3928+ else {
3929+ dom.wrapper.classList.remove( 'no-hover' );
3930+ }
3931+
3932+ if( /iphone/gi.test( UA ) ) {
3933+ dom.wrapper.classList.add( 'ua-iphone' );
3934+ }
3935+ else {
3936+ dom.wrapper.classList.remove( 'ua-iphone' );
3937+ }
3938+
3939+ // Background element
3940+ dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
3941+
3942+ // Progress bar
3943+ dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
3944+ dom.progressbar = dom.progress.querySelector( 'span' );
3945+
3946+ // Arrow controls
3947+ dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls',
3948+ '<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' +
3949+ '<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' +
3950+ '<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' +
3951+ '<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' );
3952+
3953+ // Slide number
3954+ dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
3955+
3956+ // Element containing notes that are visible to the audience
3957+ dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
3958+ dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );
3959+ dom.speakerNotes.setAttribute( 'tabindex', '0' );
3960+
3961+ // Overlay graphic which is displayed during the paused mode
3962+ dom.pauseOverlay = createSingletonNode( dom.wrapper, 'div', 'pause-overlay', '<button class="resume-button">Resume presentation</button>' );
3963+ dom.resumeButton = dom.pauseOverlay.querySelector( '.resume-button' );
3964+
3965+ dom.wrapper.setAttribute( 'role', 'application' );
3966+
3967+ // There can be multiple instances of controls throughout the page
3968+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
3969+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
3970+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
3971+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
3972+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
3973+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
3974+
3975+ // The right and down arrows in the standard reveal.js controls
3976+ dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' );
3977+ dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' );
3978+
3979+ dom.statusDiv = createStatusDiv();
3980+ }
3981+
3982+ /**
3983+ * Creates a hidden div with role aria-live to announce the
3984+ * current slide content. Hide the div off-screen to make it
3985+ * available only to Assistive Technologies.
3986+ *
3987+ * @return {HTMLElement}
3988+ */
3989+ function createStatusDiv() {
3990+
3991+ var statusDiv = document.getElementById( 'aria-status-div' );
3992+ if( !statusDiv ) {
3993+ statusDiv = document.createElement( 'div' );
3994+ statusDiv.style.position = 'absolute';
3995+ statusDiv.style.height = '1px';
3996+ statusDiv.style.width = '1px';
3997+ statusDiv.style.overflow = 'hidden';
3998+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
3999+ statusDiv.setAttribute( 'id', 'aria-status-div' );
4000+ statusDiv.setAttribute( 'aria-live', 'polite' );
4001+ statusDiv.setAttribute( 'aria-atomic','true' );
4002+ dom.wrapper.appendChild( statusDiv );
4003+ }
4004+ return statusDiv;
4005+
4006+ }
4007+
4008+ /**
4009+ * Converts the given HTML element into a string of text
4010+ * that can be announced to a screen reader. Hidden
4011+ * elements are excluded.
4012+ */
4013+ function getStatusText( node ) {
4014+
4015+ var text = '';
4016+
4017+ // Text node
4018+ if( node.nodeType === 3 ) {
4019+ text += node.textContent;
4020+ }
4021+ // Element node
4022+ else if( node.nodeType === 1 ) {
4023+
4024+ var isAriaHidden = node.getAttribute( 'aria-hidden' );
4025+ var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
4026+ if( isAriaHidden !== 'true' && !isDisplayHidden ) {
4027+
4028+ toArray( node.childNodes ).forEach( function( child ) {
4029+ text += getStatusText( child );
4030+ } );
4031+
4032+ }
4033+
4034+ }
4035+
4036+ return text;
4037+
4038+ }
4039+
4040+ /**
4041+ * Configures the presentation for printing to a static
4042+ * PDF.
4043+ */
4044+ function setupPDF() {
4045+
4046+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
4047+
4048+ // Dimensions of the PDF pages
4049+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
4050+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
4051+
4052+ // Dimensions of slides within the pages
4053+ var slideWidth = slideSize.width,
4054+ slideHeight = slideSize.height;
4055+
4056+ // Let the browser know what page size we want to print
4057+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
4058+
4059+ // Limit the size of certain elements to the dimensions of the slide
4060+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
4061+
4062+ document.body.classList.add( 'print-pdf' );
4063+ document.body.style.width = pageWidth + 'px';
4064+ document.body.style.height = pageHeight + 'px';
4065+
4066+ // Make sure stretch elements fit on slide
4067+ layoutSlideContents( slideWidth, slideHeight );
4068+
4069+ // Add each slide's index as attributes on itself, we need these
4070+ // indices to generate slide numbers below
4071+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
4072+ hslide.setAttribute( 'data-index-h', h );
4073+
4074+ if( hslide.classList.contains( 'stack' ) ) {
4075+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
4076+ vslide.setAttribute( 'data-index-h', h );
4077+ vslide.setAttribute( 'data-index-v', v );
4078+ } );
4079+ }
4080+ } );
4081+
4082+ // Slide and slide background layout
4083+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
4084+
4085+ // Vertical stacks are not centred since their section
4086+ // children will be
4087+ if( slide.classList.contains( 'stack' ) === false ) {
4088+ // Center the slide inside of the page, giving the slide some margin
4089+ var left = ( pageWidth - slideWidth ) / 2,
4090+ top = ( pageHeight - slideHeight ) / 2;
4091+
4092+ var contentHeight = slide.scrollHeight;
4093+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
4094+
4095+ // Adhere to configured pages per slide limit
4096+ numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );
4097+
4098+ // Center slides vertically
4099+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
4100+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
4101+ }
4102+
4103+ // Wrap the slide in a page element and hide its overflow
4104+ // so that no page ever flows onto another
4105+ var page = document.createElement( 'div' );
4106+ page.className = 'pdf-page';
4107+ page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
4108+ slide.parentNode.insertBefore( page, slide );
4109+ page.appendChild( slide );
4110+
4111+ // Position the slide inside of the page
4112+ slide.style.left = left + 'px';
4113+ slide.style.top = top + 'px';
4114+ slide.style.width = slideWidth + 'px';
4115+
4116+ if( slide.slideBackgroundElement ) {
4117+ page.insertBefore( slide.slideBackgroundElement, slide );
4118+ }
4119+
4120+ // Inject notes if `showNotes` is enabled
4121+ if( config.showNotes ) {
4122+
4123+ // Are there notes for this slide?
4124+ var notes = getSlideNotes( slide );
4125+ if( notes ) {
4126+
4127+ var notesSpacing = 8;
4128+ var notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
4129+ var notesElement = document.createElement( 'div' );
4130+ notesElement.classList.add( 'speaker-notes' );
4131+ notesElement.classList.add( 'speaker-notes-pdf' );
4132+ notesElement.setAttribute( 'data-layout', notesLayout );
4133+ notesElement.innerHTML = notes;
4134+
4135+ if( notesLayout === 'separate-page' ) {
4136+ page.parentNode.insertBefore( notesElement, page.nextSibling );
4137+ }
4138+ else {
4139+ notesElement.style.left = notesSpacing + 'px';
4140+ notesElement.style.bottom = notesSpacing + 'px';
4141+ notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
4142+ page.appendChild( notesElement );
4143+ }
4144+
4145+ }
4146+
4147+ }
4148+
4149+ // Inject slide numbers if `slideNumbers` are enabled
4150+ if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {
4151+ var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
4152+ slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
4153+
4154+ var numberElement = document.createElement( 'div' );
4155+ numberElement.classList.add( 'slide-number' );
4156+ numberElement.classList.add( 'slide-number-pdf' );
4157+ numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
4158+ page.appendChild( numberElement );
4159+ }
4160+
4161+ // Copy page and show fragments one after another
4162+ if( config.pdfSeparateFragments ) {
4163+
4164+ // Each fragment 'group' is an array containing one or more
4165+ // fragments. Multiple fragments that appear at the same time
4166+ // are part of the same group.
4167+ var fragmentGroups = sortFragments( page.querySelectorAll( '.fragment' ), true );
4168+
4169+ var previousFragmentStep;
4170+ var previousPage;
4171+
4172+ fragmentGroups.forEach( function( fragments ) {
4173+
4174+ // Remove 'current-fragment' from the previous group
4175+ if( previousFragmentStep ) {
4176+ previousFragmentStep.forEach( function( fragment ) {
4177+ fragment.classList.remove( 'current-fragment' );
4178+ } );
4179+ }
4180+
4181+ // Show the fragments for the current index
4182+ fragments.forEach( function( fragment ) {
4183+ fragment.classList.add( 'visible', 'current-fragment' );
4184+ } );
4185+
4186+ // Create a separate page for the current fragment state
4187+ var clonedPage = page.cloneNode( true );
4188+ page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling );
4189+
4190+ previousFragmentStep = fragments;
4191+ previousPage = clonedPage;
4192+
4193+ } );
4194+
4195+ // Reset the first/original page so that all fragments are hidden
4196+ fragmentGroups.forEach( function( fragments ) {
4197+ fragments.forEach( function( fragment ) {
4198+ fragment.classList.remove( 'visible', 'current-fragment' );
4199+ } );
4200+ } );
4201+
4202+ }
4203+ // Show all fragments
4204+ else {
4205+ toArray( page.querySelectorAll( '.fragment:not(.fade-out)' ) ).forEach( function( fragment ) {
4206+ fragment.classList.add( 'visible' );
4207+ } );
4208+ }
4209+
4210+ }
4211+
4212+ } );
4213+
4214+ // Notify subscribers that the PDF layout is good to go
4215+ dispatchEvent( 'pdf-ready' );
4216+
4217+ }
4218+
4219+ /**
4220+ * This is an unfortunate necessity. Some actions – such as
4221+ * an input field being focused in an iframe or using the
4222+ * keyboard to expand text selection beyond the bounds of
4223+ * a slide – can trigger our content to be pushed out of view.
4224+ * This scrolling can not be prevented by hiding overflow in
4225+ * CSS (we already do) so we have to resort to repeatedly
4226+ * checking if the slides have been offset :(
4227+ */
4228+ function setupScrollPrevention() {
4229+
4230+ setInterval( function() {
4231+ if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
4232+ dom.wrapper.scrollTop = 0;
4233+ dom.wrapper.scrollLeft = 0;
4234+ }
4235+ }, 1000 );
4236+
4237+ }
4238+
4239+ /**
4240+ * Creates an HTML element and returns a reference to it.
4241+ * If the element already exists the existing instance will
4242+ * be returned.
4243+ *
4244+ * @param {HTMLElement} container
4245+ * @param {string} tagname
4246+ * @param {string} classname
4247+ * @param {string} innerHTML
4248+ *
4249+ * @return {HTMLElement}
4250+ */
4251+ function createSingletonNode( container, tagname, classname, innerHTML ) {
4252+
4253+ // Find all nodes matching the description
4254+ var nodes = container.querySelectorAll( '.' + classname );
4255+
4256+ // Check all matches to find one which is a direct child of
4257+ // the specified container
4258+ for( var i = 0; i < nodes.length; i++ ) {
4259+ var testNode = nodes[i];
4260+ if( testNode.parentNode === container ) {
4261+ return testNode;
4262+ }
4263+ }
4264+
4265+ // If no node was found, create it now
4266+ var node = document.createElement( tagname );
4267+ node.className = classname;
4268+ if( typeof innerHTML === 'string' ) {
4269+ node.innerHTML = innerHTML;
4270+ }
4271+ container.appendChild( node );
4272+
4273+ return node;
4274+
4275+ }
4276+
4277+ /**
4278+ * Creates the slide background elements and appends them
4279+ * to the background container. One element is created per
4280+ * slide no matter if the given slide has visible background.
4281+ */
4282+ function createBackgrounds() {
4283+
4284+ var printMode = isPrintingPDF();
4285+
4286+ // Clear prior backgrounds
4287+ dom.background.innerHTML = '';
4288+ dom.background.classList.add( 'no-transition' );
4289+
4290+ // Iterate over all horizontal slides
4291+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
4292+
4293+ var backgroundStack = createBackground( slideh, dom.background );
4294+
4295+ // Iterate over all vertical slides
4296+ toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
4297+
4298+ createBackground( slidev, backgroundStack );
4299+
4300+ backgroundStack.classList.add( 'stack' );
4301+
4302+ } );
4303+
4304+ } );
4305+
4306+ // Add parallax background if specified
4307+ if( config.parallaxBackgroundImage ) {
4308+
4309+ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
4310+ dom.background.style.backgroundSize = config.parallaxBackgroundSize;
4311+ dom.background.style.backgroundRepeat = config.parallaxBackgroundRepeat;
4312+ dom.background.style.backgroundPosition = config.parallaxBackgroundPosition;
4313+
4314+ // Make sure the below properties are set on the element - these properties are
4315+ // needed for proper transitions to be set on the element via CSS. To remove
4316+ // annoying background slide-in effect when the presentation starts, apply
4317+ // these properties after short time delay
4318+ setTimeout( function() {
4319+ dom.wrapper.classList.add( 'has-parallax-background' );
4320+ }, 1 );
4321+
4322+ }
4323+ else {
4324+
4325+ dom.background.style.backgroundImage = '';
4326+ dom.wrapper.classList.remove( 'has-parallax-background' );
4327+
4328+ }
4329+
4330+ }
4331+
4332+ /**
4333+ * Creates a background for the given slide.
4334+ *
4335+ * @param {HTMLElement} slide
4336+ * @param {HTMLElement} container The element that the background
4337+ * should be appended to
4338+ * @return {HTMLElement} New background div
4339+ */
4340+ function createBackground( slide, container ) {
4341+
4342+
4343+ // Main slide background element
4344+ var element = document.createElement( 'div' );
4345+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
4346+
4347+ // Inner background element that wraps images/videos/iframes
4348+ var contentElement = document.createElement( 'div' );
4349+ contentElement.className = 'slide-background-content';
4350+
4351+ element.appendChild( contentElement );
4352+ container.appendChild( element );
4353+
4354+ slide.slideBackgroundElement = element;
4355+ slide.slideBackgroundContentElement = contentElement;
4356+
4357+ // Syncs the background to reflect all current background settings
4358+ syncBackground( slide );
4359+
4360+ return element;
4361+
4362+ }
4363+
4364+ /**
4365+ * Renders all of the visual properties of a slide background
4366+ * based on the various background attributes.
4367+ *
4368+ * @param {HTMLElement} slide
4369+ */
4370+ function syncBackground( slide ) {
4371+
4372+ var element = slide.slideBackgroundElement,
4373+ contentElement = slide.slideBackgroundContentElement;
4374+
4375+ // Reset the prior background state in case this is not the
4376+ // initial sync
4377+ slide.classList.remove( 'has-dark-background' );
4378+ slide.classList.remove( 'has-light-background' );
4379+
4380+ element.removeAttribute( 'data-loaded' );
4381+ element.removeAttribute( 'data-background-hash' );
4382+ element.removeAttribute( 'data-background-size' );
4383+ element.removeAttribute( 'data-background-transition' );
4384+ element.style.backgroundColor = '';
4385+
4386+ contentElement.style.backgroundSize = '';
4387+ contentElement.style.backgroundRepeat = '';
4388+ contentElement.style.backgroundPosition = '';
4389+ contentElement.style.backgroundImage = '';
4390+ contentElement.style.opacity = '';
4391+ contentElement.innerHTML = '';
4392+
4393+ var data = {
4394+ background: slide.getAttribute( 'data-background' ),
4395+ backgroundSize: slide.getAttribute( 'data-background-size' ),
4396+ backgroundImage: slide.getAttribute( 'data-background-image' ),
4397+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
4398+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
4399+ backgroundColor: slide.getAttribute( 'data-background-color' ),
4400+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
4401+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
4402+ backgroundTransition: slide.getAttribute( 'data-background-transition' ),
4403+ backgroundOpacity: slide.getAttribute( 'data-background-opacity' )
4404+ };
4405+
4406+ if( data.background ) {
4407+ // Auto-wrap image urls in url(...)
4408+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) {
4409+ slide.setAttribute( 'data-background-image', data.background );
4410+ }
4411+ else {
4412+ element.style.background = data.background;
4413+ }
4414+ }
4415+
4416+ // Create a hash for this combination of background settings.
4417+ // This is used to determine when two slide backgrounds are
4418+ // the same.
4419+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
4420+ element.setAttribute( 'data-background-hash', data.background +
4421+ data.backgroundSize +
4422+ data.backgroundImage +
4423+ data.backgroundVideo +
4424+ data.backgroundIframe +
4425+ data.backgroundColor +
4426+ data.backgroundRepeat +
4427+ data.backgroundPosition +
4428+ data.backgroundTransition +
4429+ data.backgroundOpacity );
4430+ }
4431+
4432+ // Additional and optional background properties
4433+ if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
4434+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
4435+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
4436+
4437+ // Background image options are set on the content wrapper
4438+ if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
4439+ if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
4440+ if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
4441+ if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
4442+
4443+ // If this slide has a background color, add a class that
4444+ // signals if it is light or dark. If the slide has no background
4445+ // color, no class will be set
4446+ var computedBackgroundStyle = window.getComputedStyle( element );
4447+ if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
4448+ var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
4449+
4450+ // Ignore fully transparent backgrounds. Some browsers return
4451+ // rgba(0,0,0,0) when reading the computed background color of
4452+ // an element with no background
4453+ if( rgb && rgb.a !== 0 ) {
4454+ if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
4455+ slide.classList.add( 'has-dark-background' );
4456+ }
4457+ else {
4458+ slide.classList.add( 'has-light-background' );
4459+ }
4460+ }
4461+ }
4462+
4463+ }
4464+
4465+ /**
4466+ * Registers a listener to postMessage events, this makes it
4467+ * possible to call all reveal.js API methods from another
4468+ * window. For example:
4469+ *
4470+ * revealWindow.postMessage( JSON.stringify({
4471+ * method: 'slide',
4472+ * args: [ 2 ]
4473+ * }), '*' );
4474+ */
4475+ function setupPostMessage() {
4476+
4477+ if( config.postMessage ) {
4478+ window.addEventListener( 'message', function ( event ) {
4479+ var data = event.data;
4480+
4481+ // Make sure we're dealing with JSON
4482+ if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
4483+ data = JSON.parse( data );
4484+
4485+ // Check if the requested method can be found
4486+ if( data.method && typeof Reveal[data.method] === 'function' ) {
4487+ Reveal[data.method].apply( Reveal, data.args );
4488+ }
4489+ }
4490+ }, false );
4491+ }
4492+
4493+ }
4494+
4495+ /**
4496+ * Applies the configuration settings from the config
4497+ * object. May be called multiple times.
4498+ *
4499+ * @param {object} options
4500+ */
4501+ function configure( options ) {
4502+
4503+ var oldTransition = config.transition;
4504+
4505+ // New config options may be passed when this method
4506+ // is invoked through the API after initialization
4507+ if( typeof options === 'object' ) extend( config, options );
4508+
4509+ // Abort if reveal.js hasn't finished loading, config
4510+ // changes will be applied automatically once loading
4511+ // finishes
4512+ if( loaded === false ) return;
4513+
4514+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
4515+
4516+ // Remove the previously configured transition class
4517+ dom.wrapper.classList.remove( oldTransition );
4518+
4519+ // Force linear transition based on browser capabilities
4520+ if( features.transforms3d === false ) config.transition = 'linear';
4521+
4522+ dom.wrapper.classList.add( config.transition );
4523+
4524+ dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
4525+ dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
4526+
4527+ dom.controls.style.display = config.controls ? 'block' : 'none';
4528+ dom.progress.style.display = config.progress ? 'block' : 'none';
4529+
4530+ dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout );
4531+ dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
4532+
4533+ if( config.shuffle ) {
4534+ shuffle();
4535+ }
4536+
4537+ if( config.rtl ) {
4538+ dom.wrapper.classList.add( 'rtl' );
4539+ }
4540+ else {
4541+ dom.wrapper.classList.remove( 'rtl' );
4542+ }
4543+
4544+ if( config.center ) {
4545+ dom.wrapper.classList.add( 'center' );
4546+ }
4547+ else {
4548+ dom.wrapper.classList.remove( 'center' );
4549+ }
4550+
4551+ // Exit the paused mode if it was configured off
4552+ if( config.pause === false ) {
4553+ resume();
4554+ }
4555+
4556+ if( config.showNotes ) {
4557+ dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
4558+ }
4559+
4560+ if( config.mouseWheel ) {
4561+ document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
4562+ document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
4563+ }
4564+ else {
4565+ document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
4566+ document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
4567+ }
4568+
4569+ // Rolling 3D links
4570+ if( config.rollingLinks ) {
4571+ enableRollingLinks();
4572+ }
4573+ else {
4574+ disableRollingLinks();
4575+ }
4576+
4577+ // Iframe link previews
4578+ if( config.previewLinks ) {
4579+ enablePreviewLinks();
4580+ disablePreviewLinks( '[data-preview-link=false]' );
4581+ }
4582+ else {
4583+ disablePreviewLinks();
4584+ enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
4585+ }
4586+
4587+ // Remove existing auto-slide controls
4588+ if( autoSlidePlayer ) {
4589+ autoSlidePlayer.destroy();
4590+ autoSlidePlayer = null;
4591+ }
4592+
4593+ // Generate auto-slide controls if needed
4594+ if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
4595+ autoSlidePlayer = new Playback( dom.wrapper, function() {
4596+ return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
4597+ } );
4598+
4599+ autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
4600+ autoSlidePaused = false;
4601+ }
4602+
4603+ // When fragments are turned off they should be visible
4604+ if( config.fragments === false ) {
4605+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
4606+ element.classList.add( 'visible' );
4607+ element.classList.remove( 'current-fragment' );
4608+ } );
4609+ }
4610+
4611+ // Slide numbers
4612+ var slideNumberDisplay = 'none';
4613+ if( config.slideNumber && !isPrintingPDF() ) {
4614+ if( config.showSlideNumber === 'all' ) {
4615+ slideNumberDisplay = 'block';
4616+ }
4617+ else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {
4618+ slideNumberDisplay = 'block';
4619+ }
4620+ }
4621+
4622+ dom.slideNumber.style.display = slideNumberDisplay;
4623+
4624+ sync();
4625+
4626+ }
4627+
4628+ /**
4629+ * Binds all event listeners.
4630+ */
4631+ function addEventListeners() {
4632+
4633+ eventsAreBound = true;
4634+
4635+ window.addEventListener( 'hashchange', onWindowHashChange, false );
4636+ window.addEventListener( 'resize', onWindowResize, false );
4637+
4638+ if( config.touch ) {
4639+ if( 'onpointerdown' in window ) {
4640+ // Use W3C pointer events
4641+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
4642+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
4643+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
4644+ }
4645+ else if( window.navigator.msPointerEnabled ) {
4646+ // IE 10 uses prefixed version of pointer events
4647+ dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
4648+ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
4649+ dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
4650+ }
4651+ else {
4652+ // Fall back to touch events
4653+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
4654+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
4655+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
4656+ }
4657+ }
4658+
4659+ if( config.keyboard ) {
4660+ document.addEventListener( 'keydown', onDocumentKeyDown, false );
4661+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
4662+ }
4663+
4664+ if( config.progress && dom.progress ) {
4665+ dom.progress.addEventListener( 'click', onProgressClicked, false );
4666+ }
4667+
4668+ dom.resumeButton.addEventListener( 'click', resume, false );
4669+
4670+ if( config.focusBodyOnPageVisibilityChange ) {
4671+ var visibilityChange;
4672+
4673+ if( 'hidden' in document ) {
4674+ visibilityChange = 'visibilitychange';
4675+ }
4676+ else if( 'msHidden' in document ) {
4677+ visibilityChange = 'msvisibilitychange';
4678+ }
4679+ else if( 'webkitHidden' in document ) {
4680+ visibilityChange = 'webkitvisibilitychange';
4681+ }
4682+
4683+ if( visibilityChange ) {
4684+ document.addEventListener( visibilityChange, onPageVisibilityChange, false );
4685+ }
4686+ }
4687+
4688+ // Listen to both touch and click events, in case the device
4689+ // supports both
4690+ var pointerEvents = [ 'touchstart', 'click' ];
4691+
4692+ // Only support touch for Android, fixes double navigations in
4693+ // stock browser
4694+ if( UA.match( /android/gi ) ) {
4695+ pointerEvents = [ 'touchstart' ];
4696+ }
4697+
4698+ pointerEvents.forEach( function( eventName ) {
4699+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
4700+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
4701+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
4702+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
4703+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
4704+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
4705+ } );
4706+
4707+ }
4708+
4709+ /**
4710+ * Unbinds all event listeners.
4711+ */
4712+ function removeEventListeners() {
4713+
4714+ eventsAreBound = false;
4715+
4716+ document.removeEventListener( 'keydown', onDocumentKeyDown, false );
4717+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
4718+ window.removeEventListener( 'hashchange', onWindowHashChange, false );
4719+ window.removeEventListener( 'resize', onWindowResize, false );
4720+
4721+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
4722+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
4723+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
4724+
4725+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
4726+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
4727+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
4728+
4729+ dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
4730+ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
4731+ dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
4732+
4733+ dom.resumeButton.removeEventListener( 'click', resume, false );
4734+
4735+ if ( config.progress && dom.progress ) {
4736+ dom.progress.removeEventListener( 'click', onProgressClicked, false );
4737+ }
4738+
4739+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
4740+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
4741+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
4742+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
4743+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
4744+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
4745+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
4746+ } );
4747+
4748+ }
4749+
4750+ /**
4751+ * Add a custom key binding with optional description to
4752+ * be added to the help screen.
4753+ */
4754+ function addKeyBinding( binding, callback ) {
4755+
4756+ if( typeof binding === 'object' && binding.keyCode ) {
4757+ registeredKeyBindings[binding.keyCode] = {
4758+ callback: callback,
4759+ key: binding.key,
4760+ description: binding.description
4761+ };
4762+ }
4763+ else {
4764+ registeredKeyBindings[binding] = {
4765+ callback: callback,
4766+ key: null,
4767+ description: null
4768+ };
4769+ }
4770+
4771+ }
4772+
4773+ /**
4774+ * Removes the specified custom key binding.
4775+ */
4776+ function removeKeyBinding( keyCode ) {
4777+
4778+ delete registeredKeyBindings[keyCode];
4779+
4780+ }
4781+
4782+ /**
4783+ * Extend object a with the properties of object b.
4784+ * If there's a conflict, object b takes precedence.
4785+ *
4786+ * @param {object} a
4787+ * @param {object} b
4788+ */
4789+ function extend( a, b ) {
4790+
4791+ for( var i in b ) {
4792+ a[ i ] = b[ i ];
4793+ }
4794+
4795+ return a;
4796+
4797+ }
4798+
4799+ /**
4800+ * Converts the target object to an array.
4801+ *
4802+ * @param {object} o
4803+ * @return {object[]}
4804+ */
4805+ function toArray( o ) {
4806+
4807+ return Array.prototype.slice.call( o );
4808+
4809+ }
4810+
4811+ /**
4812+ * Utility for deserializing a value.
4813+ *
4814+ * @param {*} value
4815+ * @return {*}
4816+ */
4817+ function deserialize( value ) {
4818+
4819+ if( typeof value === 'string' ) {
4820+ if( value === 'null' ) return null;
4821+ else if( value === 'true' ) return true;
4822+ else if( value === 'false' ) return false;
4823+ else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
4824+ }
4825+
4826+ return value;
4827+
4828+ }
4829+
4830+ /**
4831+ * Measures the distance in pixels between point a
4832+ * and point b.
4833+ *
4834+ * @param {object} a point with x/y properties
4835+ * @param {object} b point with x/y properties
4836+ *
4837+ * @return {number}
4838+ */
4839+ function distanceBetween( a, b ) {
4840+
4841+ var dx = a.x - b.x,
4842+ dy = a.y - b.y;
4843+
4844+ return Math.sqrt( dx*dx + dy*dy );
4845+
4846+ }
4847+
4848+ /**
4849+ * Applies a CSS transform to the target element.
4850+ *
4851+ * @param {HTMLElement} element
4852+ * @param {string} transform
4853+ */
4854+ function transformElement( element, transform ) {
4855+
4856+ element.style.WebkitTransform = transform;
4857+ element.style.MozTransform = transform;
4858+ element.style.msTransform = transform;
4859+ element.style.transform = transform;
4860+
4861+ }
4862+
4863+ /**
4864+ * Applies CSS transforms to the slides container. The container
4865+ * is transformed from two separate sources: layout and the overview
4866+ * mode.
4867+ *
4868+ * @param {object} transforms
4869+ */
4870+ function transformSlides( transforms ) {
4871+
4872+ // Pick up new transforms from arguments
4873+ if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
4874+ if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
4875+
4876+ // Apply the transforms to the slides container
4877+ if( slidesTransform.layout ) {
4878+ transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
4879+ }
4880+ else {
4881+ transformElement( dom.slides, slidesTransform.overview );
4882+ }
4883+
4884+ }
4885+
4886+ /**
4887+ * Injects the given CSS styles into the DOM.
4888+ *
4889+ * @param {string} value
4890+ */
4891+ function injectStyleSheet( value ) {
4892+
4893+ var tag = document.createElement( 'style' );
4894+ tag.type = 'text/css';
4895+ if( tag.styleSheet ) {
4896+ tag.styleSheet.cssText = value;
4897+ }
4898+ else {
4899+ tag.appendChild( document.createTextNode( value ) );
4900+ }
4901+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
4902+
4903+ }
4904+
4905+ /**
4906+ * Find the closest parent that matches the given
4907+ * selector.
4908+ *
4909+ * @param {HTMLElement} target The child element
4910+ * @param {String} selector The CSS selector to match
4911+ * the parents against
4912+ *
4913+ * @return {HTMLElement} The matched parent or null
4914+ * if no matching parent was found
4915+ */
4916+ function closestParent( target, selector ) {
4917+
4918+ var parent = target.parentNode;
4919+
4920+ while( parent ) {
4921+
4922+ // There's some overhead doing this each time, we don't
4923+ // want to rewrite the element prototype but should still
4924+ // be enough to feature detect once at startup...
4925+ var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
4926+
4927+ // If we find a match, we're all set
4928+ if( matchesMethod && matchesMethod.call( parent, selector ) ) {
4929+ return parent;
4930+ }
4931+
4932+ // Keep searching
4933+ parent = parent.parentNode;
4934+
4935+ }
4936+
4937+ return null;
4938+
4939+ }
4940+
4941+ /**
4942+ * Converts various color input formats to an {r:0,g:0,b:0} object.
4943+ *
4944+ * @param {string} color The string representation of a color
4945+ * @example
4946+ * colorToRgb('#000');
4947+ * @example
4948+ * colorToRgb('#000000');
4949+ * @example
4950+ * colorToRgb('rgb(0,0,0)');
4951+ * @example
4952+ * colorToRgb('rgba(0,0,0)');
4953+ *
4954+ * @return {{r: number, g: number, b: number, [a]: number}|null}
4955+ */
4956+ function colorToRgb( color ) {
4957+
4958+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
4959+ if( hex3 && hex3[1] ) {
4960+ hex3 = hex3[1];
4961+ return {
4962+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
4963+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
4964+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
4965+ };
4966+ }
4967+
4968+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
4969+ if( hex6 && hex6[1] ) {
4970+ hex6 = hex6[1];
4971+ return {
4972+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
4973+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
4974+ b: parseInt( hex6.substr( 4, 2 ), 16 )
4975+ };
4976+ }
4977+
4978+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
4979+ if( rgb ) {
4980+ return {
4981+ r: parseInt( rgb[1], 10 ),
4982+ g: parseInt( rgb[2], 10 ),
4983+ b: parseInt( rgb[3], 10 )
4984+ };
4985+ }
4986+
4987+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
4988+ if( rgba ) {
4989+ return {
4990+ r: parseInt( rgba[1], 10 ),
4991+ g: parseInt( rgba[2], 10 ),
4992+ b: parseInt( rgba[3], 10 ),
4993+ a: parseFloat( rgba[4] )
4994+ };
4995+ }
4996+
4997+ return null;
4998+
4999+ }
5000+
The diff has been truncated for viewing.