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

Proposed by Tim Bentley
Status: Superseded
Proposed branch: lp:~openlp-dev/openlp/webengine-migrate
Merge into: lp:openlp
Diff against target: 34449 lines (+15176/-6603)
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 (+40/-3)
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 (+746/-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 (+402/-0)
openlp/core/lib/__init__.py (+97/-309)
openlp/core/lib/db.py (+4/-3)
openlp/core/lib/htmlbuilder.py (+0/-828)
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/-193)
openlp/core/lib/settingstab.py (+4/-4)
openlp/core/lib/theme.py (+8/-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 (+6/-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 (+30/-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 (+6/-5)
openlp/core/ui/media/playertab.py (+5/-5)
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 (+19/-15)
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 (+162/-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 (+11/-11)
openlp/core/ui/themestab.py (+5/-5)
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 (+8/-7)
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 (+3/-5)
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 (+5/-4)
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 (+9/-9)
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 (+1/-0)
openlp/plugins/presentations/endpoint.py (+2/-1)
openlp/plugins/presentations/lib/impresscontroller.py (+5/-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 (+43/-16)
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 (+0/-470)
tests/functional/openlp_core/lib/test_image_manager.py (+2/-1)
tests/functional/openlp_core/lib/test_lib.py (+3/-178)
tests/functional/openlp_core/lib/test_serviceitem.py (+43/-60)
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 (+21/-16)
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 (+16/-17)
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 (+2/-1)
tests/functional/openlp_plugins/bibles/test_swordimport.py (+5/-4)
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 (+7/-8)
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+363166@code.launchpad.net

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

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

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 : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/76/ 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/77/ for more details

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

Linux tests passed!

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

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/31/ for more details

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

Linux tests passed!

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

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/32/ for more details

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

Linux tests passed!

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

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/33/ for more details

2883. By Raoul Snyman

Fix an unused import and some incorrect patch()s

2884. By Raoul Snyman

HEAD

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

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

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