1253 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1253 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
|  * Reveal.js menu plugin
 | |
|  * MIT licensed
 | |
|  * (c) Greg Denehy 2020
 | |
|  */
 | |
| 
 | |
| const Plugin = () => {
 | |
|   const ieVersion = (function () {
 | |
|     let browser = /(msie) ([\w.]+)/.exec(
 | |
|       window.navigator.userAgent.toLowerCase()
 | |
|     );
 | |
|     if (browser && browser[1] === 'msie') {
 | |
|       return parseFloat(browser[2]);
 | |
|     }
 | |
|     return null;
 | |
|   })();
 | |
| 
 | |
|   var deck;
 | |
|   var config;
 | |
|   var options;
 | |
|   var initialised = false;
 | |
| 
 | |
|   function scriptPath() {
 | |
|     // obtain plugin path from the script element
 | |
|     var path;
 | |
| 
 | |
|     const script = document.querySelector('script[src$="menu.js"]');
 | |
|     if (script) {
 | |
|       var sel = document.querySelector('script[src$="menu.js"]');
 | |
|       if (sel) {
 | |
|         path = sel.src.slice(0, -7);
 | |
|       }
 | |
|     } else {
 | |
|       path = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
 | |
|     }
 | |
| 
 | |
|     return path;
 | |
|   }
 | |
| 
 | |
|   function initOptions(config) {
 | |
|     options = config.menu || {};
 | |
|     options.path = options.path || scriptPath() || 'plugin/menu/';
 | |
|     if (!options.path.endsWith('/')) {
 | |
|       options.path += '/';
 | |
|     }
 | |
| 
 | |
|     // Set defaults
 | |
|     if (options.side === undefined) options.side = 'left';
 | |
| 
 | |
|     if (options.numbers === undefined) options.numbers = false;
 | |
| 
 | |
|     if (typeof options.titleSelector !== 'string')
 | |
|       options.titleSelector = 'h1, h2, h3, h4, h5';
 | |
| 
 | |
|     if (options.hideMissingTitles === undefined)
 | |
|       options.hideMissingTitles = false;
 | |
| 
 | |
|     if (options.useTextContentForMissingTitles === undefined)
 | |
|       options.useTextContentForMissingTitles = false;
 | |
| 
 | |
|     if (options.markers === undefined) options.markers = true;
 | |
| 
 | |
|     if (typeof options.themesPath !== 'string')
 | |
|       options.themesPath = 'dist/theme/';
 | |
|     if (!options.themesPath.endsWith('/')) options.themesPath += '/';
 | |
| 
 | |
|     if (!select('link#theme')) options.themes = false;
 | |
|     if (options.themes === true) {
 | |
|       options.themes = [
 | |
|         { name: 'Black', theme: options.themesPath + 'black.css' },
 | |
|         { name: 'White', theme: options.themesPath + 'white.css' },
 | |
|         { name: 'League', theme: options.themesPath + 'league.css' },
 | |
|         { name: 'Sky', theme: options.themesPath + 'sky.css' },
 | |
|         { name: 'Beige', theme: options.themesPath + 'beige.css' },
 | |
|         { name: 'Simple', theme: options.themesPath + 'simple.css' },
 | |
|         { name: 'Serif', theme: options.themesPath + 'serif.css' },
 | |
|         { name: 'Blood', theme: options.themesPath + 'blood.css' },
 | |
|         { name: 'Night', theme: options.themesPath + 'night.css' },
 | |
|         { name: 'Moon', theme: options.themesPath + 'moon.css' },
 | |
|         { name: 'Solarized', theme: options.themesPath + 'solarized.css' }
 | |
|       ];
 | |
|     } else if (!Array.isArray(options.themes)) {
 | |
|       options.themes = false;
 | |
|     }
 | |
| 
 | |
|     if (options.transitions === undefined) options.transitions = false;
 | |
|     if (options.transitions === true) {
 | |
|       options.transitions = [
 | |
|         'None',
 | |
|         'Fade',
 | |
|         'Slide',
 | |
|         'Convex',
 | |
|         'Concave',
 | |
|         'Zoom'
 | |
|       ];
 | |
|     } else if (
 | |
|       options.transitions !== false &&
 | |
|       (!Array.isArray(options.transitions) ||
 | |
|         !options.transitions.every(function (e) {
 | |
|           return typeof e === 'string';
 | |
|         }))
 | |
|     ) {
 | |
|       console.error(
 | |
|         "reveal.js-menu error: transitions config value must be 'true' or an array of strings, eg ['None', 'Fade', 'Slide')"
 | |
|       );
 | |
|       options.transitions = false;
 | |
|     }
 | |
|     if (ieVersion && ieVersion <= 9) {
 | |
|       // transitions aren't support in IE9 anyway, so no point in showing them
 | |
|       options.transitions = false;
 | |
|     }
 | |
| 
 | |
|     if (typeof options.openButton === 'undefined') options.openButton = true;
 | |
| 
 | |
|     if (typeof options.openSlideNumber === 'undefined')
 | |
|       options.openSlideNumber = false;
 | |
| 
 | |
|     if (typeof options.keyboard === 'undefined') options.keyboard = true;
 | |
| 
 | |
|     if (typeof options.sticky === 'undefined') options.sticky = false;
 | |
| 
 | |
|     if (typeof options.autoOpen === 'undefined') options.autoOpen = true;
 | |
| 
 | |
|     if (typeof options.delayInit === 'undefined') options.delayInit = false;
 | |
| 
 | |
|     if (typeof options.openOnInit === 'undefined') options.openOnInit = false;
 | |
|   }
 | |
| 
 | |
|   var mouseSelectionEnabled = true;
 | |
|   function disableMouseSelection() {
 | |
|     mouseSelectionEnabled = false;
 | |
|   }
 | |
| 
 | |
|   function reenableMouseSelection() {
 | |
|     // wait until the mouse has moved before re-enabling mouse selection
 | |
|     // to avoid selections on scroll
 | |
|     select('nav.slide-menu').addEventListener('mousemove', function fn(e) {
 | |
|       select('nav.slide-menu').removeEventListener('mousemove', fn);
 | |
|       //XXX this should select the item under the mouse
 | |
|       mouseSelectionEnabled = true;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Keyboard handling
 | |
|   //
 | |
|   function getOffset(el) {
 | |
|     var _x = 0;
 | |
|     var _y = 0;
 | |
|     while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
 | |
|       _x += el.offsetLeft - el.scrollLeft;
 | |
|       _y += el.offsetTop - el.scrollTop;
 | |
|       el = el.offsetParent;
 | |
|     }
 | |
|     return { top: _y, left: _x };
 | |
|   }
 | |
| 
 | |
|   function visibleOffset(el) {
 | |
|     var offsetFromTop = getOffset(el).top - el.offsetParent.offsetTop;
 | |
|     if (offsetFromTop < 0) return -offsetFromTop;
 | |
|     var offsetFromBottom =
 | |
|       el.offsetParent.offsetHeight -
 | |
|       (el.offsetTop - el.offsetParent.scrollTop + el.offsetHeight);
 | |
|     if (offsetFromBottom < 0) return offsetFromBottom;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   function keepVisible(el) {
 | |
|     var offset = visibleOffset(el);
 | |
|     if (offset) {
 | |
|       disableMouseSelection();
 | |
|       el.scrollIntoView(offset > 0);
 | |
|       reenableMouseSelection();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function scrollItemToTop(el) {
 | |
|     disableMouseSelection();
 | |
|     el.offsetParent.scrollTop = el.offsetTop;
 | |
|     reenableMouseSelection();
 | |
|   }
 | |
| 
 | |
|   function scrollItemToBottom(el) {
 | |
|     disableMouseSelection();
 | |
|     el.offsetParent.scrollTop =
 | |
|       el.offsetTop - el.offsetParent.offsetHeight + el.offsetHeight;
 | |
|     reenableMouseSelection();
 | |
|   }
 | |
| 
 | |
|   function selectItem(el) {
 | |
|     el.classList.add('selected');
 | |
|     keepVisible(el);
 | |
|     if (options.sticky && options.autoOpen) openItem(el);
 | |
|   }
 | |
| 
 | |
|   function onDocumentKeyDown(event) {
 | |
|     // opening menu is handled by registering key binding with Reveal below
 | |
|     if (isOpen()) {
 | |
|       event.stopImmediatePropagation();
 | |
|       switch (event.keyCode) {
 | |
|         // case 77:
 | |
|         // 	closeMenu();
 | |
|         // 	break;
 | |
|         // h, left - change panel
 | |
|         case 72:
 | |
|         case 37:
 | |
|           prevPanel();
 | |
|           break;
 | |
|         // l, right - change panel
 | |
|         case 76:
 | |
|         case 39:
 | |
|           nextPanel();
 | |
|           break;
 | |
|         // k, up
 | |
|         case 75:
 | |
|         case 38:
 | |
|           var currItem =
 | |
|             select('.active-menu-panel .slide-menu-items li.selected') ||
 | |
|             select('.active-menu-panel .slide-menu-items li.active');
 | |
|           if (currItem) {
 | |
|             selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|               function (item) {
 | |
|                 item.classList.remove('selected');
 | |
|               }
 | |
|             );
 | |
|             var nextItem =
 | |
|               select(
 | |
|                 '.active-menu-panel .slide-menu-items li[data-item="' +
 | |
|                   (parseInt(currItem.getAttribute('data-item')) - 1) +
 | |
|                   '"]'
 | |
|               ) || currItem;
 | |
|             selectItem(nextItem);
 | |
|           } else {
 | |
|             var item = select(
 | |
|               '.active-menu-panel .slide-menu-items li.slide-menu-item'
 | |
|             );
 | |
|             if (item) selectItem(item);
 | |
|           }
 | |
|           break;
 | |
|         // j, down
 | |
|         case 74:
 | |
|         case 40:
 | |
|           var currItem =
 | |
|             select('.active-menu-panel .slide-menu-items li.selected') ||
 | |
|             select('.active-menu-panel .slide-menu-items li.active');
 | |
|           if (currItem) {
 | |
|             selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|               function (item) {
 | |
|                 item.classList.remove('selected');
 | |
|               }
 | |
|             );
 | |
|             var nextItem =
 | |
|               select(
 | |
|                 '.active-menu-panel .slide-menu-items li[data-item="' +
 | |
|                   (parseInt(currItem.getAttribute('data-item')) + 1) +
 | |
|                   '"]'
 | |
|               ) || currItem;
 | |
|             selectItem(nextItem);
 | |
|           } else {
 | |
|             var item = select(
 | |
|               '.active-menu-panel .slide-menu-items li.slide-menu-item'
 | |
|             );
 | |
|             if (item) selectItem(item);
 | |
|           }
 | |
|           break;
 | |
|         // pageup, u
 | |
|         case 33:
 | |
|         case 85:
 | |
|           var itemsAbove = selectAll(
 | |
|             '.active-menu-panel .slide-menu-items li'
 | |
|           ).filter(function (item) {
 | |
|             return visibleOffset(item) > 0;
 | |
|           });
 | |
|           var visibleItems = selectAll(
 | |
|             '.active-menu-panel .slide-menu-items li'
 | |
|           ).filter(function (item) {
 | |
|             return visibleOffset(item) == 0;
 | |
|           });
 | |
| 
 | |
|           var firstVisible =
 | |
|             itemsAbove.length > 0 &&
 | |
|             Math.abs(visibleOffset(itemsAbove[itemsAbove.length - 1])) <
 | |
|               itemsAbove[itemsAbove.length - 1].clientHeight
 | |
|               ? itemsAbove[itemsAbove.length - 1]
 | |
|               : visibleItems[0];
 | |
|           if (firstVisible) {
 | |
|             if (
 | |
|               firstVisible.classList.contains('selected') &&
 | |
|               itemsAbove.length > 0
 | |
|             ) {
 | |
|               // at top of viewport already, page scroll (if not at start)
 | |
|               // ...move selected item to bottom, and change selection to last fully visible item at top
 | |
|               scrollItemToBottom(firstVisible);
 | |
|               visibleItems = selectAll(
 | |
|                 '.active-menu-panel .slide-menu-items li'
 | |
|               ).filter(function (item) {
 | |
|                 return visibleOffset(item) == 0;
 | |
|               });
 | |
|               if (visibleItems[0] == firstVisible) {
 | |
|                 // prev item is still beyond the viewport (for custom panels)
 | |
|                 firstVisible = itemsAbove[itemsAbove.length - 1];
 | |
|               } else {
 | |
|                 firstVisible = visibleItems[0];
 | |
|               }
 | |
|             }
 | |
|             selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|               function (item) {
 | |
|                 item.classList.remove('selected');
 | |
|               }
 | |
|             );
 | |
|             selectItem(firstVisible);
 | |
|             // ensure selected item is positioned at the top of the viewport
 | |
|             scrollItemToTop(firstVisible);
 | |
|           }
 | |
|           break;
 | |
|         // pagedown, d
 | |
|         case 34:
 | |
|         case 68:
 | |
|           var visibleItems = selectAll(
 | |
|             '.active-menu-panel .slide-menu-items li'
 | |
|           ).filter(function (item) {
 | |
|             return visibleOffset(item) == 0;
 | |
|           });
 | |
|           var itemsBelow = selectAll(
 | |
|             '.active-menu-panel .slide-menu-items li'
 | |
|           ).filter(function (item) {
 | |
|             return visibleOffset(item) < 0;
 | |
|           });
 | |
| 
 | |
|           var lastVisible =
 | |
|             itemsBelow.length > 0 &&
 | |
|             Math.abs(visibleOffset(itemsBelow[0])) < itemsBelow[0].clientHeight
 | |
|               ? itemsBelow[0]
 | |
|               : visibleItems[visibleItems.length - 1];
 | |
|           if (lastVisible) {
 | |
|             if (
 | |
|               lastVisible.classList.contains('selected') &&
 | |
|               itemsBelow.length > 0
 | |
|             ) {
 | |
|               // at bottom of viewport already, page scroll (if not at end)
 | |
|               // ...move selected item to top, and change selection to last fully visible item at bottom
 | |
|               scrollItemToTop(lastVisible);
 | |
|               visibleItems = selectAll(
 | |
|                 '.active-menu-panel .slide-menu-items li'
 | |
|               ).filter(function (item) {
 | |
|                 return visibleOffset(item) == 0;
 | |
|               });
 | |
|               if (visibleItems[visibleItems.length - 1] == lastVisible) {
 | |
|                 // next item is still beyond the viewport (for custom panels)
 | |
|                 lastVisible = itemsBelow[0];
 | |
|               } else {
 | |
|                 lastVisible = visibleItems[visibleItems.length - 1];
 | |
|               }
 | |
|             }
 | |
|             selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|               function (item) {
 | |
|                 item.classList.remove('selected');
 | |
|               }
 | |
|             );
 | |
|             selectItem(lastVisible);
 | |
|             // ensure selected item is positioned at the bottom of the viewport
 | |
|             scrollItemToBottom(lastVisible);
 | |
|           }
 | |
|           break;
 | |
|         // home
 | |
|         case 36:
 | |
|           selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|             function (item) {
 | |
|               item.classList.remove('selected');
 | |
|             }
 | |
|           );
 | |
|           var item = select(
 | |
|             '.active-menu-panel .slide-menu-items li:first-of-type'
 | |
|           );
 | |
|           if (item) {
 | |
|             item.classList.add('selected');
 | |
|             keepVisible(item);
 | |
|           }
 | |
|           break;
 | |
|         // end
 | |
|         case 35:
 | |
|           selectAll('.active-menu-panel .slide-menu-items li').forEach(
 | |
|             function (item) {
 | |
|               item.classList.remove('selected');
 | |
|             }
 | |
|           );
 | |
|           var item = select(
 | |
|             '.active-menu-panel .slide-menu-items:last-of-type li:last-of-type'
 | |
|           );
 | |
|           if (item) {
 | |
|             item.classList.add('selected');
 | |
|             keepVisible(item);
 | |
|           }
 | |
|           break;
 | |
|         // space, return
 | |
|         case 32:
 | |
|         case 13:
 | |
|           var currItem = select(
 | |
|             '.active-menu-panel .slide-menu-items li.selected'
 | |
|           );
 | |
|           if (currItem) {
 | |
|             openItem(currItem, true);
 | |
|           }
 | |
|           break;
 | |
|         // esc
 | |
|         case 27:
 | |
|           closeMenu(null, true);
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Utilty functions
 | |
|   //
 | |
| 
 | |
|   function openMenu(event) {
 | |
|     if (event) event.preventDefault();
 | |
|     if (!isOpen()) {
 | |
|       select('body').classList.add('slide-menu-active');
 | |
|       select('.reveal').classList.add(
 | |
|         'has-' + options.effect + '-' + options.side
 | |
|       );
 | |
|       select('.slide-menu').classList.add('active');
 | |
|       select('.slide-menu-overlay').classList.add('active');
 | |
| 
 | |
|       // identify active theme
 | |
|       if (options.themes) {
 | |
|         selectAll('div[data-panel="Themes"] li').forEach(function (i) {
 | |
|           i.classList.remove('active');
 | |
|         });
 | |
|         selectAll(
 | |
|           'li[data-theme="' + select('link#theme').getAttribute('href') + '"]'
 | |
|         ).forEach(function (i) {
 | |
|           i.classList.add('active');
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       // identify active transition
 | |
|       if (options.transitions) {
 | |
|         selectAll('div[data-panel="Transitions"] li').forEach(function (i) {
 | |
|           i.classList.remove('active');
 | |
|         });
 | |
|         selectAll('li[data-transition="' + config.transition + '"]').forEach(
 | |
|           function (i) {
 | |
|             i.classList.add('active');
 | |
|           }
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       // set item selections to match active items
 | |
|       var items = selectAll('.slide-menu-panel li.active');
 | |
|       items.forEach(function (i) {
 | |
|         i.classList.add('selected');
 | |
|         keepVisible(i);
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function closeMenu(event, force) {
 | |
|     if (event) event.preventDefault();
 | |
|     if (!options.sticky || force) {
 | |
|       select('body').classList.remove('slide-menu-active');
 | |
|       select('.reveal').classList.remove(
 | |
|         'has-' + options.effect + '-' + options.side
 | |
|       );
 | |
|       select('.slide-menu').classList.remove('active');
 | |
|       select('.slide-menu-overlay').classList.remove('active');
 | |
|       selectAll('.slide-menu-panel li.selected').forEach(function (i) {
 | |
|         i.classList.remove('selected');
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function toggleMenu(event) {
 | |
|     if (isOpen()) {
 | |
|       closeMenu(event, true);
 | |
|     } else {
 | |
|       openMenu(event);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function isOpen() {
 | |
|     return select('body').classList.contains('slide-menu-active');
 | |
|   }
 | |
| 
 | |
|   function openPanel(event, ref) {
 | |
|     openMenu(event);
 | |
|     var panel = ref;
 | |
|     if (typeof ref !== 'string') {
 | |
|       panel = event.currentTarget.getAttribute('data-panel');
 | |
|     }
 | |
|     select('.slide-menu-toolbar > li.active-toolbar-button').classList.remove(
 | |
|       'active-toolbar-button'
 | |
|     );
 | |
|     select('li[data-panel="' + panel + '"]').classList.add(
 | |
|       'active-toolbar-button'
 | |
|     );
 | |
|     select('.slide-menu-panel.active-menu-panel').classList.remove(
 | |
|       'active-menu-panel'
 | |
|     );
 | |
|     select('div[data-panel="' + panel + '"]').classList.add(
 | |
|       'active-menu-panel'
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   function nextPanel() {
 | |
|     var next =
 | |
|       (parseInt(select('.active-toolbar-button').getAttribute('data-button')) +
 | |
|         1) %
 | |
|       buttons;
 | |
|     openPanel(
 | |
|       null,
 | |
|       select('.toolbar-panel-button[data-button="' + next + '"]').getAttribute(
 | |
|         'data-panel'
 | |
|       )
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   function prevPanel() {
 | |
|     var next =
 | |
|       parseInt(select('.active-toolbar-button').getAttribute('data-button')) -
 | |
|       1;
 | |
|     if (next < 0) {
 | |
|       next = buttons - 1;
 | |
|     }
 | |
|     openPanel(
 | |
|       null,
 | |
|       select('.toolbar-panel-button[data-button="' + next + '"]').getAttribute(
 | |
|         'data-panel'
 | |
|       )
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   function openItem(item, force) {
 | |
|     var h = parseInt(item.getAttribute('data-slide-h'));
 | |
|     var v = parseInt(item.getAttribute('data-slide-v'));
 | |
|     var theme = item.getAttribute('data-theme');
 | |
|     var highlightTheme = item.getAttribute('data-highlight-theme');
 | |
|     var transition = item.getAttribute('data-transition');
 | |
| 
 | |
|     if (!isNaN(h) && !isNaN(v)) {
 | |
|       deck.slide(h, v);
 | |
|     }
 | |
| 
 | |
|     if (theme) {
 | |
|       changeStylesheet('theme', theme);
 | |
|     }
 | |
| 
 | |
|     if (highlightTheme) {
 | |
|       changeStylesheet('highlight-theme', highlightTheme);
 | |
|     }
 | |
| 
 | |
|     if (transition) {
 | |
|       deck.configure({ transition: transition });
 | |
|     }
 | |
| 
 | |
|     var link = select('a', item);
 | |
|     if (link) {
 | |
|       if (
 | |
|         force ||
 | |
|         !options.sticky ||
 | |
|         (options.autoOpen && link.href.startsWith('#')) ||
 | |
|         link.href.startsWith(
 | |
|           window.location.origin + window.location.pathname + '#'
 | |
|         )
 | |
|       ) {
 | |
|         link.click();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     closeMenu();
 | |
|   }
 | |
| 
 | |
|   function clicked(event) {
 | |
|     if (event.target.nodeName !== 'A') {
 | |
|       event.preventDefault();
 | |
|     }
 | |
|     openItem(event.currentTarget);
 | |
|   }
 | |
| 
 | |
|   function highlightCurrentSlide() {
 | |
|     var state = deck.getState();
 | |
|     selectAll('li.slide-menu-item, li.slide-menu-item-vertical').forEach(
 | |
|       function (item) {
 | |
|         item.classList.remove('past');
 | |
|         item.classList.remove('active');
 | |
|         item.classList.remove('future');
 | |
| 
 | |
|         var h = parseInt(item.getAttribute('data-slide-h'));
 | |
|         var v = parseInt(item.getAttribute('data-slide-v'));
 | |
|         if (h < state.indexh || (h === state.indexh && v < state.indexv)) {
 | |
|           item.classList.add('past');
 | |
|         } else if (h === state.indexh && v === state.indexv) {
 | |
|           item.classList.add('active');
 | |
|         } else {
 | |
|           item.classList.add('future');
 | |
|         }
 | |
|       }
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   function matchRevealStyle() {
 | |
|     var revealStyle = window.getComputedStyle(select('.reveal'));
 | |
|     var element = select('.slide-menu');
 | |
|     element.style.fontFamily = revealStyle.fontFamily;
 | |
|     //XXX could adjust the complete menu style to match the theme, ie colors, etc
 | |
|   }
 | |
| 
 | |
|   var buttons = 0;
 | |
|   function initMenu() {
 | |
|     if (!initialised) {
 | |
|       var parent = select('.reveal').parentElement;
 | |
|       var top = create('div', { class: 'slide-menu-wrapper' });
 | |
|       parent.appendChild(top);
 | |
|       var panels = create('nav', {
 | |
|         class: 'slide-menu slide-menu--' + options.side
 | |
|       });
 | |
|       if (typeof options.width === 'string') {
 | |
|         if (
 | |
|           ['normal', 'wide', 'third', 'half', 'full'].indexOf(options.width) !=
 | |
|           -1
 | |
|         ) {
 | |
|           panels.classList.add('slide-menu--' + options.width);
 | |
|         } else {
 | |
|           panels.classList.add('slide-menu--custom');
 | |
|           panels.style.width = options.width;
 | |
|         }
 | |
|       }
 | |
|       top.appendChild(panels);
 | |
|       matchRevealStyle();
 | |
|       var overlay = create('div', { class: 'slide-menu-overlay' });
 | |
|       top.appendChild(overlay);
 | |
|       overlay.onclick = function () {
 | |
|         closeMenu(null, true);
 | |
|       };
 | |
| 
 | |
|       var toolbar = create('ol', { class: 'slide-menu-toolbar' });
 | |
|       select('.slide-menu').appendChild(toolbar);
 | |
| 
 | |
|       function addToolbarButton(title, ref, icon, style, fn, active) {
 | |
|         var attrs = {
 | |
|           'data-button': '' + buttons++,
 | |
|           class:
 | |
|             'toolbar-panel-button' + (active ? ' active-toolbar-button' : '')
 | |
|         };
 | |
|         if (ref) {
 | |
|           attrs['data-panel'] = ref;
 | |
|         }
 | |
|         var button = create('li', attrs);
 | |
| 
 | |
|         if (icon.startsWith('fa-')) {
 | |
|           button.appendChild(create('i', { class: style + ' ' + icon }));
 | |
|         } else {
 | |
|           button.innerHTML = icon + '</i>';
 | |
|         }
 | |
|         button.appendChild(create('br'), select('i', button));
 | |
|         button.appendChild(
 | |
|           create('span', { class: 'slide-menu-toolbar-label' }, title),
 | |
|           select('i', button)
 | |
|         );
 | |
|         button.onclick = fn;
 | |
|         toolbar.appendChild(button);
 | |
|         return button;
 | |
|       }
 | |
| 
 | |
|       addToolbarButton('Slides', 'Slides', 'fa-images', 'fas', openPanel, true);
 | |
| 
 | |
|       if (options.custom) {
 | |
|         options.custom.forEach(function (element, index, array) {
 | |
|           addToolbarButton(
 | |
|             element.title,
 | |
|             'Custom' + index,
 | |
|             element.icon,
 | |
|             null,
 | |
|             openPanel
 | |
|           );
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       if (options.themes) {
 | |
|         addToolbarButton('Themes', 'Themes', 'fa-adjust', 'fas', openPanel);
 | |
|       }
 | |
|       if (options.transitions) {
 | |
|         addToolbarButton(
 | |
|           'Transitions',
 | |
|           'Transitions',
 | |
|           'fa-sticky-note',
 | |
|           'fas',
 | |
|           openPanel
 | |
|         );
 | |
|       }
 | |
|       var button = create('li', {
 | |
|         id: 'close',
 | |
|         class: 'toolbar-panel-button'
 | |
|       });
 | |
|       button.appendChild(create('i', { class: 'fas fa-times' }));
 | |
|       button.appendChild(create('br'));
 | |
|       button.appendChild(
 | |
|         create('span', { class: 'slide-menu-toolbar-label' }, 'Close')
 | |
|       );
 | |
|       button.onclick = function () {
 | |
|         closeMenu(null, true);
 | |
|       };
 | |
|       toolbar.appendChild(button);
 | |
| 
 | |
|       //
 | |
|       // Slide links
 | |
|       //
 | |
|       function generateItem(type, section, i, h, v) {
 | |
|         var link = '/#/' + h;
 | |
|         if (typeof v === 'number' && !isNaN(v)) link += '/' + v;
 | |
| 
 | |
|         function text(selector, parent) {
 | |
|           if (selector === '') return null;
 | |
|           var el = parent ? select(selector, section) : select(selector);
 | |
|           if (el) return el.textContent;
 | |
|           return null;
 | |
|         }
 | |
|         var title =
 | |
|           section.getAttribute('data-menu-title') ||
 | |
|           text('.menu-title', section) ||
 | |
|           text(options.titleSelector, section);
 | |
| 
 | |
|         if (!title && options.useTextContentForMissingTitles) {
 | |
|           // attempt to figure out a title based on the text in the slide
 | |
|           title = section.textContent.trim();
 | |
|           if (title) {
 | |
|             title =
 | |
|               title
 | |
|                 .split('\n')
 | |
|                 .map(function (t) {
 | |
|                   return t.trim();
 | |
|                 })
 | |
|                 .join(' ')
 | |
|                 .trim()
 | |
|                 .replace(/^(.{16}[^\s]*).*/, '$1') // limit to 16 chars plus any consecutive non-whitespace chars (to avoid breaking words)
 | |
|                 .replace(/&/g, '&')
 | |
|                 .replace(/</g, '<')
 | |
|                 .replace(/>/g, '>')
 | |
|                 .replace(/"/g, '"')
 | |
|                 .replace(/'/g, ''') + '...';
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (!title) {
 | |
|           if (options.hideMissingTitles) return '';
 | |
|           type += ' no-title';
 | |
|           title = 'Slide ' + (i + 1);
 | |
|         }
 | |
| 
 | |
|         var item = create('li', {
 | |
|           class: type,
 | |
|           'data-item': i,
 | |
|           'data-slide-h': h,
 | |
|           'data-slide-v': v === undefined ? 0 : v
 | |
|         });
 | |
| 
 | |
|         if (options.markers) {
 | |
|           item.appendChild(
 | |
|             create('i', { class: 'fas fa-check-circle fa-fw past' })
 | |
|           );
 | |
|           item.appendChild(
 | |
|             create('i', {
 | |
|               class: 'fas fa-arrow-alt-circle-right fa-fw active'
 | |
|             })
 | |
|           );
 | |
|           item.appendChild(
 | |
|             create('i', { class: 'far fa-circle fa-fw future' })
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         if (options.numbers) {
 | |
|           // Number formatting taken from reveal.js
 | |
|           var value = [];
 | |
|           var format = 'h.v';
 | |
| 
 | |
|           // Check if a custom number format is available
 | |
|           if (typeof options.numbers === 'string') {
 | |
|             format = options.numbers;
 | |
|           } else if (typeof config.slideNumber === 'string') {
 | |
|             // Take user defined number format for slides
 | |
|             format = config.slideNumber;
 | |
|           }
 | |
| 
 | |
|           switch (format) {
 | |
|             case 'c':
 | |
|               value.push(i + 1);
 | |
|               break;
 | |
|             case 'c/t':
 | |
|               value.push(i + 1, '/', deck.getTotalSlides());
 | |
|               break;
 | |
|             case 'h/v':
 | |
|               value.push(h + 1);
 | |
|               if (typeof v === 'number' && !isNaN(v)) value.push('/', v + 1);
 | |
|               break;
 | |
|             default:
 | |
|               value.push(h + 1);
 | |
|               if (typeof v === 'number' && !isNaN(v)) value.push('.', v + 1);
 | |
|           }
 | |
| 
 | |
|           item.appendChild(
 | |
|             create(
 | |
|               'span',
 | |
|               { class: 'slide-menu-item-number' },
 | |
|               value.join('') + '. '
 | |
|             )
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         item.appendChild(
 | |
|           create('span', { class: 'slide-menu-item-title' }, title)
 | |
|         );
 | |
| 
 | |
|         return item;
 | |
|       }
 | |
| 
 | |
|       function createSlideMenu() {
 | |
|         if (
 | |
|           !document.querySelector(
 | |
|             'section[data-markdown]:not([data-markdown-parsed])'
 | |
|           )
 | |
|         ) {
 | |
|           var panel = create('div', {
 | |
|             'data-panel': 'Slides',
 | |
|             class: 'slide-menu-panel active-menu-panel'
 | |
|           });
 | |
|           panel.appendChild(create('ul', { class: 'slide-menu-items' }));
 | |
|           panels.appendChild(panel);
 | |
|           var items = select(
 | |
|             '.slide-menu-panel[data-panel="Slides"] > .slide-menu-items'
 | |
|           );
 | |
|           var slideCount = 0;
 | |
|           selectAll('.slides > section').forEach(function (section, h) {
 | |
|             var subsections = selectAll('section', section);
 | |
|             if (subsections.length > 0) {
 | |
|               subsections.forEach(function (subsection, v) {
 | |
|                 var type =
 | |
|                   v === 0 ? 'slide-menu-item' : 'slide-menu-item-vertical';
 | |
|                 var item = generateItem(type, subsection, slideCount, h, v);
 | |
|                 if (item) {
 | |
|                   items.appendChild(item);
 | |
|                 }
 | |
|                 slideCount++;
 | |
|               });
 | |
|             } else {
 | |
|               var item = generateItem(
 | |
|                 'slide-menu-item',
 | |
|                 section,
 | |
|                 slideCount,
 | |
|                 h
 | |
|               );
 | |
|               if (item) {
 | |
|                 items.appendChild(item);
 | |
|               }
 | |
|               slideCount++;
 | |
|             }
 | |
|           });
 | |
|           selectAll('.slide-menu-item, .slide-menu-item-vertical').forEach(
 | |
|             function (i) {
 | |
|               i.onclick = clicked;
 | |
|             }
 | |
|           );
 | |
|           highlightCurrentSlide();
 | |
|         } else {
 | |
|           // wait for markdown to be loaded and parsed
 | |
|           setTimeout(createSlideMenu, 100);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       createSlideMenu();
 | |
|       deck.addEventListener('slidechanged', highlightCurrentSlide);
 | |
| 
 | |
|       //
 | |
|       // Custom menu panels
 | |
|       //
 | |
|       if (options.custom) {
 | |
|         function xhrSuccess() {
 | |
|           if (this.status >= 200 && this.status < 300) {
 | |
|             this.panel.innerHTML = this.responseText;
 | |
|             enableCustomLinks(this.panel);
 | |
|           } else {
 | |
|             showErrorMsg(this);
 | |
|           }
 | |
|         }
 | |
|         function xhrError() {
 | |
|           showErrorMsg(this);
 | |
|         }
 | |
|         function loadCustomPanelContent(panel, sURL) {
 | |
|           var oReq = new XMLHttpRequest();
 | |
|           oReq.panel = panel;
 | |
|           oReq.arguments = Array.prototype.slice.call(arguments, 2);
 | |
|           oReq.onload = xhrSuccess;
 | |
|           oReq.onerror = xhrError;
 | |
|           oReq.open('get', sURL, true);
 | |
|           oReq.send(null);
 | |
|         }
 | |
|         function enableCustomLinks(panel) {
 | |
|           selectAll('ul.slide-menu-items li.slide-menu-item', panel).forEach(
 | |
|             function (item, i) {
 | |
|               item.setAttribute('data-item', i + 1);
 | |
|               item.onclick = clicked;
 | |
|               item.addEventListener('mouseenter', handleMouseHighlight);
 | |
|             }
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         function showErrorMsg(response) {
 | |
|           var msg =
 | |
|             '<p>ERROR: The attempt to fetch ' +
 | |
|             response.responseURL +
 | |
|             ' failed with HTTP status ' +
 | |
|             response.status +
 | |
|             ' (' +
 | |
|             response.statusText +
 | |
|             ').</p>' +
 | |
|             '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>';
 | |
|           response.panel.innerHTML = msg;
 | |
|         }
 | |
| 
 | |
|         options.custom.forEach(function (element, index, array) {
 | |
|           var panel = create('div', {
 | |
|             'data-panel': 'Custom' + index,
 | |
|             class: 'slide-menu-panel slide-menu-custom-panel'
 | |
|           });
 | |
|           if (element.content) {
 | |
|             panel.innerHTML = element.content;
 | |
|             enableCustomLinks(panel);
 | |
|           } else if (element.src) {
 | |
|             loadCustomPanelContent(panel, element.src);
 | |
|           }
 | |
|           panels.appendChild(panel);
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Themes
 | |
|       //
 | |
|       if (options.themes) {
 | |
|         var panel = create('div', {
 | |
|           class: 'slide-menu-panel',
 | |
|           'data-panel': 'Themes'
 | |
|         });
 | |
|         panels.appendChild(panel);
 | |
|         var menu = create('ul', { class: 'slide-menu-items' });
 | |
|         panel.appendChild(menu);
 | |
|         options.themes.forEach(function (t, i) {
 | |
|           var attrs = {
 | |
|             class: 'slide-menu-item',
 | |
|             'data-item': '' + (i + 1)
 | |
|           };
 | |
|           if (t.theme) {
 | |
|             attrs['data-theme'] = t.theme;
 | |
|           }
 | |
|           if (t.highlightTheme) {
 | |
|             attrs['data-highlight-theme'] = t.highlightTheme;
 | |
|           }
 | |
|           var item = create('li', attrs, t.name);
 | |
|           menu.appendChild(item);
 | |
|           item.onclick = clicked;
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Transitions
 | |
|       //
 | |
|       if (options.transitions) {
 | |
|         var panel = create('div', {
 | |
|           class: 'slide-menu-panel',
 | |
|           'data-panel': 'Transitions'
 | |
|         });
 | |
|         panels.appendChild(panel);
 | |
|         var menu = create('ul', { class: 'slide-menu-items' });
 | |
|         panel.appendChild(menu);
 | |
|         options.transitions.forEach(function (name, i) {
 | |
|           var item = create(
 | |
|             'li',
 | |
|             {
 | |
|               class: 'slide-menu-item',
 | |
|               'data-transition': name.toLowerCase(),
 | |
|               'data-item': '' + (i + 1)
 | |
|             },
 | |
|             name
 | |
|           );
 | |
|           menu.appendChild(item);
 | |
|           item.onclick = clicked;
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Open menu options
 | |
|       //
 | |
|       if (options.openButton) {
 | |
|         // add menu button
 | |
|         var div = create('div', { class: 'slide-menu-button' });
 | |
|         var link = create('a', { href: '#' });
 | |
|         link.appendChild(create('i', { class: 'fas fa-bars' }));
 | |
|         div.appendChild(link);
 | |
|         select('.reveal').appendChild(div);
 | |
|         div.onclick = openMenu;
 | |
|       }
 | |
| 
 | |
|       if (options.openSlideNumber) {
 | |
|         var slideNumber = select('div.slide-number');
 | |
|         slideNumber.onclick = openMenu;
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Handle mouse overs
 | |
|       //
 | |
|       selectAll('.slide-menu-panel .slide-menu-items li').forEach(function (
 | |
|         item
 | |
|       ) {
 | |
|         item.addEventListener('mouseenter', handleMouseHighlight);
 | |
|       });
 | |
| 
 | |
|       function handleMouseHighlight(event) {
 | |
|         if (mouseSelectionEnabled) {
 | |
|           selectAll('.active-menu-panel .slide-menu-items li.selected').forEach(
 | |
|             function (i) {
 | |
|               i.classList.remove('selected');
 | |
|             }
 | |
|           );
 | |
|           event.currentTarget.classList.add('selected');
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (options.keyboard) {
 | |
|       //XXX add keyboard option for custom key codes, etc.
 | |
| 
 | |
|       document.addEventListener('keydown', onDocumentKeyDown, false);
 | |
| 
 | |
|       // handle key presses within speaker notes
 | |
|       window.addEventListener('message', function (event) {
 | |
|         var data;
 | |
|         try {
 | |
|           data = JSON.parse(event.data);
 | |
|         } catch (e) {}
 | |
|         if (data && data.method === 'triggerKey') {
 | |
|           onDocumentKeyDown({
 | |
|             keyCode: data.args[0],
 | |
|             stopImmediatePropagation: function () {}
 | |
|           });
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       // Prevent reveal from processing keyboard events when the menu is open
 | |
|       if (
 | |
|         config.keyboardCondition &&
 | |
|         typeof config.keyboardCondition === 'function'
 | |
|       ) {
 | |
|         // combine user defined keyboard condition with the menu's own condition
 | |
|         var userCondition = config.keyboardCondition;
 | |
|         config.keyboardCondition = function (event) {
 | |
|           return userCondition(event) && (!isOpen() || event.keyCode == 77);
 | |
|         };
 | |
|       } else {
 | |
|         config.keyboardCondition = function (event) {
 | |
|           return !isOpen() || event.keyCode == 77;
 | |
|         };
 | |
|       }
 | |
| 
 | |
|       deck.addKeyBinding(
 | |
|         { keyCode: 77, key: 'M', description: 'Toggle menu' },
 | |
|         toggleMenu
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (options.openOnInit) {
 | |
|       openMenu();
 | |
|     }
 | |
| 
 | |
|     initialised = true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Extend object a with the properties of object b.
 | |
|    * If there's a conflict, object b takes precedence.
 | |
|    */
 | |
|   function extend(a, b) {
 | |
|     for (var i in b) {
 | |
|       a[i] = b[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Dispatches an event of the specified type from the
 | |
|    * reveal DOM element.
 | |
|    */
 | |
|   function dispatchEvent(type, args) {
 | |
|     var event = document.createEvent('HTMLEvents', 1, 2);
 | |
|     event.initEvent(type, true, true);
 | |
|     extend(event, args);
 | |
|     document.querySelector('.reveal').dispatchEvent(event);
 | |
| 
 | |
|     // If we're in an iframe, post each reveal.js event to the
 | |
|     // parent window. Used by the notes plugin
 | |
|     if (config.postMessageEvents && window.parent !== window.self) {
 | |
|       window.parent.postMessage(
 | |
|         JSON.stringify({
 | |
|           namespace: 'reveal',
 | |
|           eventName: type,
 | |
|           state: deck.getState()
 | |
|         }),
 | |
|         '*'
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function select(selector, el) {
 | |
|     if (!el) {
 | |
|       el = document;
 | |
|     }
 | |
|     return el.querySelector(selector);
 | |
|   }
 | |
| 
 | |
|   function selectAll(selector, el) {
 | |
|     if (!el) {
 | |
|       el = document;
 | |
|     }
 | |
|     return Array.prototype.slice.call(el.querySelectorAll(selector));
 | |
|   }
 | |
| 
 | |
|   function create(tagName, attrs, content) {
 | |
|     var el = document.createElement(tagName);
 | |
|     if (attrs) {
 | |
|       Object.getOwnPropertyNames(attrs).forEach(function (n) {
 | |
|         el.setAttribute(n, attrs[n]);
 | |
|       });
 | |
|     }
 | |
|     if (content) el.innerHTML = content;
 | |
|     return el;
 | |
|   }
 | |
| 
 | |
|   function changeStylesheet(id, href) {
 | |
|     // take note of the previous theme and remove it, then create a new stylesheet reference and insert it
 | |
|     // this is required to force a load event so we can change the menu style to match the new style
 | |
|     var stylesheet = select('link#' + id);
 | |
|     var parent = stylesheet.parentElement;
 | |
|     var sibling = stylesheet.nextElementSibling;
 | |
|     stylesheet.remove();
 | |
| 
 | |
|     var newStylesheet = stylesheet.cloneNode();
 | |
|     newStylesheet.setAttribute('href', href);
 | |
|     newStylesheet.onload = function () {
 | |
|       matchRevealStyle();
 | |
|     };
 | |
|     parent.insertBefore(newStylesheet, sibling);
 | |
|   }
 | |
| 
 | |
|   // modified from math plugin
 | |
|   function loadResource(url, type, callback) {
 | |
|     var head = document.querySelector('head');
 | |
|     var resource;
 | |
| 
 | |
|     if (type === 'script') {
 | |
|       resource = document.createElement('script');
 | |
|       resource.type = 'text/javascript';
 | |
|       resource.src = url;
 | |
|     } else if (type === 'stylesheet') {
 | |
|       resource = document.createElement('link');
 | |
|       resource.rel = 'stylesheet';
 | |
|       resource.href = url;
 | |
|     }
 | |
| 
 | |
|     // Wrapper for callback to make sure it only fires once
 | |
|     var finish = function () {
 | |
|       if (typeof callback === 'function') {
 | |
|         callback.call();
 | |
|         callback = null;
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     resource.onload = finish;
 | |
| 
 | |
|     // IE
 | |
|     resource.onreadystatechange = function () {
 | |
|       if (this.readyState === 'loaded') {
 | |
|         finish();
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     // Normal browsers
 | |
|     head.appendChild(resource);
 | |
|   }
 | |
| 
 | |
|   function loadPlugin() {
 | |
|     // does not support IE8 or below
 | |
|     var supported = !ieVersion || ieVersion >= 9;
 | |
| 
 | |
|     // do not load the menu in the upcoming slide panel in the speaker notes
 | |
|     if (
 | |
|       deck.isSpeakerNotes() &&
 | |
|       window.location.search.endsWith('controls=false')
 | |
|     ) {
 | |
|       supported = false;
 | |
|     }
 | |
| 
 | |
|     if (supported) {
 | |
|       if (!options.delayInit) initMenu();
 | |
|       dispatchEvent('menu-ready');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     id: 'menu',
 | |
|     init: reveal => {
 | |
|       deck = reveal;
 | |
|       config = deck.getConfig();
 | |
|       initOptions(config);
 | |
|       loadResource(options.path + 'menu.css', 'stylesheet', function () {
 | |
|         if (options.loadIcons === undefined || options.loadIcons) {
 | |
|           loadResource(
 | |
|             options.path + 'font-awesome/css/all.css',
 | |
|             'stylesheet',
 | |
|             loadPlugin
 | |
|           );
 | |
|         } else {
 | |
|           loadPlugin();
 | |
|         }
 | |
|       });
 | |
|     },
 | |
| 
 | |
|     toggle: toggleMenu,
 | |
|     openMenu: openMenu,
 | |
|     closeMenu: closeMenu,
 | |
|     openPanel: openPanel,
 | |
|     isOpen: isOpen,
 | |
|     initialiseMenu: initMenu,
 | |
|     isMenuInitialised: function () {
 | |
|       return initialised;
 | |
|     }
 | |
|   };
 | |
| };
 | |
| 
 | |
| // polyfill
 | |
| if (!String.prototype.startsWith) {
 | |
|   String.prototype.startsWith = function (searchString, position) {
 | |
|     return this.substr(position || 0, searchString.length) === searchString;
 | |
|   };
 | |
| }
 | |
| if (!String.prototype.endsWith) {
 | |
|   String.prototype.endsWith = function (search, this_len) {
 | |
|     if (this_len === undefined || this_len > this.length) {
 | |
|       this_len = this.length;
 | |
|     }
 | |
|     return this.substring(this_len - search.length, this_len) === search;
 | |
|   };
 | |
| }
 | |
| 
 | |
| export default Plugin;
 |