blob: 65f05eef44a5f5722b2d101d7a37afd275f6320d [file] [log] [blame]
JJ Allaire064552c2017-02-10 10:28:41 -05001/*
2 * Reveal.js menu plugin
3 * MIT licensed
4 * (c) Greg Denehy 2015
5 */
6
7var RevealMenu = window.RevealMenu || (function(){
8 var config = Reveal.getConfig();
9 var options = config.menu || {};
10 options.path = options.path || scriptPath() || 'plugin/menu';
11
12 var module = {};
13
14 loadResource(options.path + '/lib/jeesh.min.js', 'script', function() {
15 loadResource(options.path + '/lib/bowser.min.js', 'script', function() {
16 loadResource(options.path + '/menu.css', 'stylesheet', function() {
17 // does not support IE8 or below
18 if (!bowser.msie || bowser.version >= 9) {
19 //
20 // Set option defaults
21 //
22 var side = options.side || 'left'; // 'left' or 'right'
23 var numbers = options.numbers || false;
24 var titleSelector = 'h1, h2, h3, h4, h5';
25 if (typeof options.titleSelector === 'string') titleSelector = options.titleSelector;
26 var hideMissingTitles = options.hideMissingTitles || false;
27 var markers = options.markers || false;
28 var custom = options.custom;
29 var themes = options.themes;
30 if (typeof themes === "undefined") {
31 themes = [
32 { name: 'Black', theme: 'css/theme/black.css' },
33 { name: 'White', theme: 'css/theme/white.css' },
34 { name: 'League', theme: 'css/theme/league.css' },
35 { name: 'Sky', theme: 'css/theme/sky.css' },
36 { name: 'Beige', theme: 'css/theme/beige.css' },
37 { name: 'Simple', theme: 'css/theme/simple.css' },
38 { name: 'Serif', theme: 'css/theme/serif.css' },
39 { name: 'Blood', theme: 'css/theme/blood.css' },
40 { name: 'Night', theme: 'css/theme/night.css' },
41 { name: 'Moon', theme: 'css/theme/moon.css' },
42 { name: 'Solarized', theme: 'css/theme/solarized.css' }
43 ];
44 }
45 var transitions = options.transitions;
46 if (typeof transitions === "undefined") transitions = true;
47 if (bowser.msie && bowser.version <= 9) {
48 // transitions aren't support in IE9 anyway, so no point in showing them
49 transitions = false;
50 }
51 var openButton = options.openButton;
52 if (typeof openButton === "undefined") openButton = true;
53 var openSlideNumber = options.openSlideNumber;
54 if (typeof openSlideNumber === "undefined") openSlideNumber = false;
55 var keyboard = options.keyboard;
56 if (typeof keyboard === "undefined") keyboard = true;
57
58 function disableMouseSelection() {
59 mouseSelectionEnabled = false;
60 }
61
62 function reenableMouseSelection() {
63 // wait until the mouse has moved before re-enabling mouse selection
64 // to avoid selections on scroll
65 $('nav.slide-menu').one('mousemove', function(event) {
66 //XXX this should select the item under the mouse
67 mouseSelectionEnabled = true;
68 });
69 }
70
71 //
72 // Keyboard handling
73 //
74 function getOffset(el) {
75 var _x = 0;
76 var _y = 0;
77 while(el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
78 _x += el.offsetLeft - el.scrollLeft;
79 _y += el.offsetTop - el.scrollTop;
80 el = el.offsetParent;
81 }
82 return { top: _y, left: _x };
83 }
84
85 function visibleOffset(el) {
86 var offsetFromTop = getOffset(el).top - el.offsetParent.offsetTop;
87 if (offsetFromTop < 0) return -offsetFromTop
88 var offsetFromBottom = el.offsetParent.offsetHeight - (el.offsetTop - el.offsetParent.scrollTop + el.offsetHeight);
89 if (offsetFromBottom < 0) return offsetFromBottom;
90 return 0;
91 }
92
93 function keepVisible(el) {
94 var offset = visibleOffset(el);
95 if (offset) {
96 disableMouseSelection();
97 el.scrollIntoView(offset > 0);
98 reenableMouseSelection();
99 }
100 }
101
102 function scrollItemToTop(el) {
103 disableMouseSelection();
104 el.offsetParent.scrollTop = el.offsetTop;
105 reenableMouseSelection();
106 }
107
108 function scrollItemToBottom(el) {
109 disableMouseSelection();
110 el.offsetParent.scrollTop = el.offsetTop - el.offsetParent.offsetHeight + el.offsetHeight
111 reenableMouseSelection();
112 }
113
114 function selectItem(el) {
115 $(el).addClass('selected');
116 keepVisible(el);
117 }
118
119 function onDocumentKeyDown(event) {
120 if (event.keyCode === 77) {
121 toggleMenu();
122 } else if (isOpen()) {
123 event.stopImmediatePropagation();
124 switch( event.keyCode ) {
125 // h, left - change panel
126 case 72: case 37:
127 prevPanel();
128 break;
129 // l, right - change panel
130 case 76: case 39:
131 nextPanel();
132 break;
133 // k, up
134 case 75: case 38:
135 var currItem = $('.active-menu-panel .slide-menu-items li.selected').get(0) || $('.active-menu-panel .slide-menu-items li.active').get(0);
136 if (currItem) {
137 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
138 var nextItem = $('.active-menu-panel .slide-menu-items li[data-item="' + ($(currItem).data('item') - 1) + '"]').get(0) || currItem;
139 selectItem(nextItem);
140 } else {
141 var items = $('.active-menu-panel .slide-menu-items li.slide-menu-item');
142 if (items.length > 0) {
143 selectItem(items.get(0));
144 }
145 }
146 break;
147 // j, down
148 case 74: case 40:
149 var currItem = $('.active-menu-panel .slide-menu-items li.selected').get(0) || $('.active-menu-panel .slide-menu-items li.active').get(0);
150 if (currItem) {
151 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
152 var nextItem = $('.active-menu-panel .slide-menu-items li[data-item="' + ($(currItem).data('item') + 1) + '"]').get(0) || currItem;
153 selectItem(nextItem);
154 } else {
155 var items = $('.active-menu-panel .slide-menu-items li.slide-menu-item');
156 if (items.length > 0) {
157 selectItem(items.get(0));
158 }
159 }
160 break;
161 // pageup, u
162 case 33: case 85:
163 var itemsAbove = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) > 0; });
164 var visibleItems = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) == 0; });
165
166 var firstVisible = (itemsAbove.length > 0 && Math.abs(visibleOffset(itemsAbove[itemsAbove.length-1])) < itemsAbove[itemsAbove.length-1].clientHeight ? itemsAbove[itemsAbove.length-1] : visibleItems[0]);
167 if (firstVisible) {
168 if ($(firstVisible).hasClass('selected') && itemsAbove.length > 0) {
169 // at top of viewport already, page scroll (if not at start)
170 // ...move selected item to bottom, and change selection to last fully visible item at top
171 scrollItemToBottom(firstVisible);
172 visibleItems = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) == 0; });
173 if (visibleItems[0] == firstVisible) {
174 // prev item is still beyond the viewport (for custom panels)
175 firstVisible = itemsAbove[itemsAbove.length-1];
176 } else {
177 firstVisible = visibleItems[0];
178 }
179 }
180 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
181 selectItem(firstVisible);
182 // ensure selected item is positioned at the top of the viewport
183 scrollItemToTop(firstVisible);
184 }
185 break;
186 // pagedown, d
187 case 34: case 68:
188 var visibleItems = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) == 0; });
189 var itemsBelow = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) < 0; });
190
191 var lastVisible = (itemsBelow.length > 0 && Math.abs(visibleOffset(itemsBelow[0])) < itemsBelow[0].clientHeight ? itemsBelow[0] : visibleItems[visibleItems.length-1]);
192 if (lastVisible) {
193 if ($(lastVisible).hasClass('selected') && itemsBelow.length > 0) {
194 // at bottom of viewport already, page scroll (if not at end)
195 // ...move selected item to top, and change selection to last fully visible item at bottom
196 scrollItemToTop(lastVisible);
197 visibleItems = $('.active-menu-panel .slide-menu-items li').filter(function(item) { return visibleOffset(item) == 0; });
198 if (visibleItems[visibleItems.length-1] == lastVisible) {
199 // next item is still beyond the viewport (for custom panels)
200 lastVisible = itemsBelow[0];
201 } else {
202 lastVisible = visibleItems[visibleItems.length-1];
203 }
204 }
205 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
206 selectItem(lastVisible);
207 // ensure selected item is positioned at the bottom of the viewport
208 scrollItemToBottom(lastVisible);
209 }
210 break;
211 // home
212 case 36:
213 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
214 var sel = $('.active-menu-panel .slide-menu-items li:first-of-type');
215 if (sel.length > 0) {
216 keepVisible(sel.addClass('selected').get(0));
217 }
218 break;
219 // end
220 case 35:
221 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
222 var sel = $('.active-menu-panel .slide-menu-items li:last-of-type');
223 if (sel.length > 0) {
224 keepVisible(sel.addClass('selected').get(0));
225 }
226 break;
227 // space, return
228 case 32: case 13:
229 var currItem = $('.active-menu-panel .slide-menu-items li.selected').get(0);
230 if (currItem) {
231 openItem(currItem);
232 }
233 break;
234 // esc
235 case 27: closeMenu(); break;
236 }
237 }
238 }
239
240 if (keyboard) {
241 //XXX add keyboard option for custom key codes, etc.
242
243 document.addEventListener('keydown', onDocumentKeyDown, false);
244
245 // handle key presses within speaker notes
246 window.addEventListener( 'message', function( event ) {
247 var data = JSON.parse( event.data );
248 if (data.method === 'triggerKey') {
249 onDocumentKeyDown( { keyCode: data.args[0], stopImmediatePropagation: function() {} } );
250 }
251 });
252
253 // Prevent reveal from processing keyboard events when the menu is open
254 if (config.keyboardCondition && typeof config.keyboardCondition === 'function') {
255 // combine user defined keyboard condition with the menu's own condition
256 var userCondition = config.keyboardCondition;
257 config.keyboardCondition = function() {
258 return userCondition() && !isOpen();
259 };
260 } else {
261 config.keyboardCondition = function() { return !isOpen(); }
262 }
263 }
264
265
266 //
267 // Utilty functions
268 //
269
270 function openMenu(event) {
271 if (event) event.preventDefault();
272 if (!isOpen()) {
273 $('body').addClass('slide-menu-active');
274 $('.reveal').addClass('has-' + options.effect + '-' + side);
275 $('.slide-menu').addClass('active');
276 $('.slide-menu-overlay').addClass('active');
277
278 // identify active theme
279 $('div[data-panel="Themes"] li').removeClass('active');
280 $('li[data-theme="' + $('#theme').attr('href') + '"]').addClass('active');
281
282 // identify active transition
283 $('div[data-panel="Transitions"] li').removeClass('active');
284 $('li[data-transition="' + Reveal.getConfig().transition + '"]').addClass('active');
285
286 // set item selections to match active items
287 $('.slide-menu-panel li.active')
288 .addClass('selected')
289 .each(function(item) { keepVisible(item) });
290 }
291 }
292
293 function closeMenu(event) {
294 if (event) event.preventDefault();
295 $('body').removeClass('slide-menu-active');
296 $('.reveal').removeClass('has-' + options.effect + '-' + side);
297 $('.slide-menu').removeClass('active');
298 $('.slide-menu-overlay').removeClass('active');
299 $('.slide-menu-panel li.selected').removeClass('selected');
300 }
301
302 function toggleMenu(event) {
303 if (isOpen()) {
304 closeMenu(event);
305 } else {
306 openMenu(event);
307 }
308 }
309
310 function isOpen() {
311 return $('body').hasClass('slide-menu-active');
312 }
313
314 function openPanel(e) {
315 openMenu();
316 var panel = e;
317 if (typeof e !== "string") {
318 panel = $(e.currentTarget).data('panel');
319 }
320 $('.slide-menu-toolbar > li').removeClass('active-toolbar-button');
321 $('li[data-panel="' + panel + '"]').addClass('active-toolbar-button');
322 $('.slide-menu-panel').removeClass('active-menu-panel');
323 $('div[data-panel="' + panel + '"]').addClass('active-menu-panel');
324 }
325
326 function nextPanel() {
327 var next = ($('.active-toolbar-button').data('button') + 1) % buttons;
328 openPanel($('.toolbar-panel-button[data-button="' + next + '"]').data('panel'));
329 }
330
331 function prevPanel() {
332 var next = $('.active-toolbar-button').data('button') - 1;
333 if (next < 0) {
334 next = buttons - 1;
335 }
336 openPanel($('.toolbar-panel-button[data-button="' + next + '"]').data('panel'));
337 }
338
339 $('<nav class="slide-menu slide-menu--' + side + '"></nav>')
340 .appendTo($('.reveal'));
341 $('<div class="slide-menu-overlay"></div>')
342 .appendTo($('.reveal'))
343 .click(closeMenu);
344
345 var toolbar = $('<ol class="slide-menu-toolbar"></ol>').prependTo($('.slide-menu'));
346 var buttons = 0;
347 $('<li data-panel="Slides" data-button="' + (buttons++) + '" class="toolbar-panel-button"><span class="slide-menu-toolbar-label">Slides</span><br/><i class="fa fa-list"></i></li>')
348 .appendTo(toolbar)
349 .addClass('active-toolbar-button')
350 .click(openPanel);
351
352 if (custom) {
353 custom.forEach(function(element, index, array) {
354 $('<li data-panel="Custom' + index + '" data-button="' + (buttons++) + '" class="toolbar-panel-button"><span class="slide-menu-toolbar-label">' + element.title + '</span><br/>' + element.icon + '</i></li>')
355 .appendTo(toolbar)
356 .click(openPanel);
357 })
358 }
359
360 if (themes) {
361 $('<li data-panel="Themes" data-button="' + (buttons++) + '" class="toolbar-panel-button"><span class="slide-menu-toolbar-label">Themes</span><br/><i class="fa fa-desktop"></i></li>')
362 .appendTo(toolbar)
363 .click(openPanel);
364 }
365 if (transitions) {
366 $('<li data-panel="Transitions" data-button="' + (buttons++) + '" class="toolbar-panel-button"><span class="slide-menu-toolbar-label">Transitions</span><br/><i class="fa fa-arrows-h"></i></li>')
367 .appendTo(toolbar)
368 .click(openPanel);
369 }
370 $('<li id="close"><span class="slide-menu-toolbar-label">Close</span><br/><i class="fa fa-times"></i></li>')
371 .appendTo(toolbar)
372 .click(closeMenu);
373
374 var panels = $('.slide-menu');
375
376 //
377 // Slide links
378 //
379 function generateItem(type, section, i, h, v) {
380 var link = '/#/' + h;
381 if (typeof v === 'number' && !isNaN( v )) link += '/' + v;
382
383 var title = $(section).data('menu-title') ||
384 $('.menu-title', section).text() ||
385 $(titleSelector, section).text();
386 if (!title) {
387 if (hideMissingTitles) return '';
388 title = "Slide " + i;
389 type += ' no-title';
390 }
391
392 title = '<span class="slide-menu-item-title">' + title + '</span>';
393 if (numbers) {
394 // Number formatting taken from reveal.js
395 var value = [];
396 var format = 'h.v';
397
398 // Check if a custom number format is available
399 if( typeof numbers === 'string' ) {
400 format = numbers;
401 }
402 else if (typeof config.slideNumber === 'string') {
403 // Take user defined number format for slides
404 format = config.slideNumber;
405 }
406
407 switch( format ) {
408 case 'c':
409 value.push( i );
410 break;
411 case 'c/t':
412 value.push( i, '/', Reveal.getTotalSlides() );
413 break;
414 case 'h/v':
415 value.push( h + 1 );
416 if( typeof v === 'number' && !isNaN( v ) ) value.push( '/', v + 1 );
417 break;
418 default:
419 value.push( h + 1 );
420 if( typeof v === 'number' && !isNaN( v ) ) value.push( '.', v + 1 );
421 }
422
423 title = '<span class="slide-menu-item-number">' + value.join('') + '. </span>' + title;
424 }
425
426 var m = '';
427 if (markers) {
428 m = '<i class="fa fa-check-circle past"></i>' +
429 '<i class="fa fa-dot-circle-o active"></i>' +
430 '<i class="fa fa-circle-thin future"></i>';
431 }
432
433 return '<li class="' + type + '" data-item="' + i + '" data-slide-h="' + h + '" data-slide-v="' + (v === undefined ? 0 : v) + '">' + m + title + '</li>';
434 }
435
436 function openItem(item) {
437 var h = $(item).data('slide-h');
438 var v = $(item).data('slide-v');
439 var theme = $(item).data('theme');
440 var transition = $(item).data('transition');
441 if (typeof h !== "undefined" && typeof v !== "undefined") {
442 Reveal.slide(h, v);
443 closeMenu();
444 } else if (theme) {
445 $('#theme').attr('href', theme);
446 closeMenu();
447 } else if (transition) {
448 Reveal.configure({ transition: transition });
449 closeMenu();
450 } else {
451 var links = $(item).find('a');
452 if (links.length > 0) {
453 links.get(0).click();
454 }
455 closeMenu();
456 }
457 }
458
459 function clicked(event) {
460 if (event.target.nodeName !== "A") {
461 event.preventDefault();
462 }
463 openItem(event.currentTarget);
464 }
465
466 function highlightCurrentSlide() {
467 var state = Reveal.getState();
468 $('li.slide-menu-item, li.slide-menu-item-vertical')
469 .removeClass('past')
470 .removeClass('active')
471 .removeClass('future');
472
473 $('li.slide-menu-item, li.slide-menu-item-vertical').each(function(e) {
474 var h = $(e).data('slide-h');
475 var v = $(e).data('slide-v');
476 if (h < state.indexh || (h === state.indexh && v < state.indexv)) {
477 $(e).addClass('past');
478 }
479 else if (h === state.indexh && v === state.indexv) {
480 $(e).addClass('active');
481 }
482 else {
483 $(e).addClass('future');
484 }
485 });
486 }
487
488 function createSlideMenu() {
489 if ( !document.querySelector('section[data-markdown]:not([data-markdown-parsed])') ) {
490 $('<div data-panel="Slides" class="slide-menu-panel"><ul class="slide-menu-items"></ul></div>')
491 .appendTo(panels)
492 .addClass('active-menu-panel');
493 var items = $('.slide-menu-panel[data-panel="Slides"] > .slide-menu-items');
494 var slideCount = 0;
495 $('.slides > section').each(function(section, h) {
496 var subsections = $('section', section);
497 if (subsections.length > 0) {
498 subsections.each(function(subsection, v) {
499 var type = (v === 0 ? 'slide-menu-item' : 'slide-menu-item-vertical');
500 var item = generateItem(type, subsection, slideCount, h, v);
501 if (item) {
502 slideCount++;
503 items.append(item);
504 }
505 });
506 } else {
507 var item = generateItem('slide-menu-item', section, slideCount, h);
508 if (item) {
509 slideCount++;
510 items.append(item);
511 }
512 }
513 });
514 $('.slide-menu-item, .slide-menu-item-vertical').click(clicked);
515 highlightCurrentSlide();
516 }
517 else {
518 // wait for markdown to be loaded and parsed
519 setTimeout( createSlideMenu, 100 );
520 }
521 }
522
523 createSlideMenu();
524 Reveal.addEventListener('slidechanged', highlightCurrentSlide);
525
526 //
527 // Custom menu panels
528 //
529 if (custom) {
530 function xhrSuccess () {
531 if (this.status >= 200 && this.status < 300) {
532 $(this.responseText).appendTo(this.panel);
533 enableCustomLinks(this.panel);
534 }
535 else {
536 showErrorMsg(this)
537 }
538 }
539 function xhrError () {
540 showErrorMsg(this)
541 }
542 function loadCustomPanelContent (panel, sURL) {
543 var oReq = new XMLHttpRequest();
544 oReq.panel = panel;
545 oReq.arguments = Array.prototype.slice.call(arguments, 2);
546 oReq.onload = xhrSuccess;
547 oReq.onerror = xhrError;
548 oReq.open("get", sURL, true);
549 oReq.send(null);
550 }
551 function enableCustomLinks(panel) {
552 $(panel).find('ul.slide-menu-items li.slide-menu-item').each(function(item, i) {
553 $(item).attr('data-item', i+1);
554 $(item).click(clicked);
555 });
556 }
557 function showErrorMsg(response) {
558 var msg = '<p>ERROR: The attempt to fetch ' + response.responseURL + ' failed with HTTP status ' +
559 response.status + ' (' + response.statusText + ').</p>' +
560 '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>';
561 $(msg).appendTo(response.panel)
562 }
563
564 custom.forEach(function(element, index, array) {
565 var panel = $('<div data-panel="Custom' + index + '" class="slide-menu-panel slide-menu-custom-panel"></div>');
566 if (element.content) {
567 $(element.content).appendTo(panel);
568 enableCustomLinks(panel);
569 }
570 else if (element.src) {
571 loadCustomPanelContent(panel, element.src);
572 }
573 panel.appendTo(panels);
574 })
575 }
576
577 //
578 // Themes
579 //
580 if (themes) {
581 var panel = $('<div data-panel="Themes" class="slide-menu-panel"></div>').appendTo(panels);
582 var menu = $('<ul class="slide-menu-items"></ul>').appendTo(panel);
583 themes.forEach(function(t, i) {
584 $('<li class="slide-menu-item" data-theme="' + t.theme + '" data-item="' + (i+1) + '">' + t.name + '</li>').appendTo(menu).click(clicked);
585 })
586 }
587
588 //
589 // Transitions
590 //
591 if (transitions) {
592 var panel = $('<div data-panel="Transitions" class="slide-menu-panel"></div>').appendTo(panels);
593 var menu = $('<ul class="slide-menu-items"></ul>').appendTo(panel);
594 ['None', 'Fade', 'Slide', 'Convex', 'Concave', 'Zoom', 'Cube', 'Page'].forEach(function(name, i) {
595 $('<li class="slide-menu-item" data-transition="' + name.toLowerCase() + '" data-item="' + (i+1) + '">' + name + '</li>').appendTo(menu).click(clicked);
596 })
597 }
598
599 //
600 // Open menu options
601 //
602 if (openButton) {
603 // add menu button
604 $('<div class="slide-menu-button"><a href="#"><i class="fa fa-bars"></i></a></div>')
605 .appendTo($('.reveal'))
606 .click(openMenu);
607 }
608
609 if (openSlideNumber) {
610 // wrap slide number in link
611 $('<div class="slide-number-wrapper"><a href="#"></a></div>').insertAfter($('div.slide-number'));
612 $('.slide-number').appendTo($('.slide-number-wrapper a'));
613 $('.slide-number-wrapper a').click(openMenu);
614 }
615
616 //
617 // Handle mouse overs
618 //
619 var mouseSelectionEnabled = true;
620 $('.slide-menu-panel .slide-menu-items li').mouseenter(function(event) {
621 if (mouseSelectionEnabled) {
622 $('.active-menu-panel .slide-menu-items li').removeClass('selected');
623 $(event.currentTarget).addClass('selected');
624 }
625 });
626
627 module.toggle = toggleMenu;
628 module.isOpen = isOpen;
629
630 /**
631 * Extend object a with the properties of object b.
632 * If there's a conflict, object b takes precedence.
633 */
634 function extend( a, b ) {
635 for( var i in b ) {
636 a[ i ] = b[ i ];
637 }
638 }
639
640 /**
641 * Dispatches an event of the specified type from the
642 * reveal DOM element.
643 */
644 function dispatchEvent( type, args ) {
645 var event = document.createEvent( 'HTMLEvents', 1, 2 );
646 event.initEvent( type, true, true );
647 extend( event, args );
648 document.querySelector('.reveal').dispatchEvent( event );
649
650 // If we're in an iframe, post each reveal.js event to the
651 // parent window. Used by the notes plugin
652 if( config.postMessageEvents && window.parent !== window.self ) {
653 window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
654 }
655 }
656
657 dispatchEvent('menu-ready');
658 }
659 })
660 })
661 });
662
663 // modified from math plugin
664 function loadResource( url, type, callback ) {
665 var head = document.querySelector( 'head' );
666 var resource;
667
668 if ( type === 'script' ) {
669 resource = document.createElement( 'script' );
670 resource.type = 'text/javascript';
671 resource.src = url;
672 }
673 else if ( type === 'stylesheet' ) {
674 resource = document.createElement( 'link' );
675 resource.rel = 'stylesheet';
676 resource.href = url;
677 }
678
679 // Wrapper for callback to make sure it only fires once
680 var finish = function() {
681 if( typeof callback === 'function' ) {
682 callback.call();
683 callback = null;
684 }
685 }
686
687 resource.onload = finish;
688
689 // IE
690 resource.onreadystatechange = function() {
691 if ( this.readyState === 'loaded' ) {
692 finish();
693 }
694 }
695
696 // Normal browsers
697 head.appendChild( resource );
698 }
699
700 function scriptPath() {
701 // obtain plugin path from the script element
702 var path;
703 if (document.currentScript) {
704 path = document.currentScript.src.slice(0, -7);
705 } else {
706 var sel = document.querySelector('script[src$="/menu.js"]')
707 if (sel) {
708 path = sel.src.slice(0, -7);
709 }
710 }
711 return path;
712 }
713
714 return module;
715})();