showoff 0.12.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,8 @@ var notesWindow = null;
5
5
 
6
6
  var paceData = [];
7
7
 
8
+ section = 'notes'; // which section the presenter has chosen to view
9
+
8
10
  $(document).ready(function(){
9
11
  // set up the presenter modes
10
12
  mode = { track: true, follow: true, update: true, slave: false, next: false, notes: false};
@@ -16,9 +18,9 @@ $(document).ready(function(){
16
18
  // the presenter window doesn't need the reload on resize bit
17
19
  $(window).unbind('resize');
18
20
 
19
- $("#minStop").hide();
20
- $("#startTimer").click(function() { toggleTimer() });
21
- $("#stopTimer").click(function() { toggleTimer() });
21
+ $("#startTimer").click(function() { startTimer() });
22
+ $("#pauseTimer").click(function() { toggleTimer() });
23
+ $("#stopTimer").click(function() { stopTimer() });
22
24
 
23
25
  /* zoom slide to match preview size, then set up resize handler. */
24
26
  zoom();
@@ -83,7 +85,8 @@ function presenterPopupToggle(page, event) {
83
85
 
84
86
  content.attr('id', page.substring(1, page.length));
85
87
  content.append(link);
86
- content.append($(data).find('#wrapper').html());
88
+ /* use .sibliings() because of how jquery formats $(data) */
89
+ content.append($(data).siblings('#wrapper').html());
87
90
  popup.append(content);
88
91
 
89
92
  setupStats(); // this function is in showoff.js because /stats does not load presenter.js
@@ -102,14 +105,14 @@ function reportIssue() {
102
105
 
103
106
  // open browser to remote edit URL
104
107
  function editSlide() {
105
- var slide = $("span#slideFile").text().replace(/\/\d+$/, '');
108
+ var slide = $("span#slideFile").text().replace(/:\d+$/, '');
106
109
  var link = editUrl + slide + ".md";
107
110
  window.open(link);
108
111
  }
109
112
 
110
113
  // call the edit endpoint to open up a local file editor
111
114
  function openEditor() {
112
- var slide = $("span#slideFile").text().replace(/\/\d+$/, '');
115
+ var slide = $("span#slideFile").text().replace(/:\d+$/, '');
113
116
  var link = '/edit/' + slide + ".md";
114
117
  $.get(link);
115
118
  }
@@ -245,12 +248,31 @@ function printSlides()
245
248
  }
246
249
  }
247
250
 
248
- function askQuestion(question) {
249
- $("#questions ul").prepend($('<li/>').text(question));
251
+ function postQuestion(question, questionID) {
252
+ var questionItem = $('<li/>').text(question).attr('id', questionID);
250
253
 
251
- $('#questions ul li:first-child').click( function(e) {
252
- $(this).remove();
253
- });
254
+ questionItem.click( function(e) {
255
+ markCompleted($(this).attr('id'));
256
+ removeQuestion(questionID);
257
+ });
258
+
259
+ $("#unanswered").append(questionItem);
260
+ updateQuestionIndicator();
261
+ }
262
+
263
+ function removeQuestion(questionID) {
264
+ var question = $("li#"+questionID);
265
+ question.toggleClass('answered')
266
+ .remove();
267
+ $('#answered').append($(question));
268
+ updateQuestionIndicator();
269
+ }
270
+
271
+ function updateQuestionIndicator() {
272
+ try {
273
+ slaveWindow.updateQuestionIndicator( $('#unanswered li').length )
274
+ }
275
+ catch (e) {}
254
276
  }
255
277
 
256
278
  function paceFeedback(pace) {
@@ -330,6 +352,10 @@ reconnectControlChannel = function() {
330
352
  });
331
353
  }
332
354
 
355
+ function markCompleted(questionID) {
356
+ ws.send(JSON.stringify({ message: 'complete', questionID: questionID}));
357
+ }
358
+
333
359
  function update() {
334
360
  if(mode.update) {
335
361
  var slideName = $("#slideFile").text();
@@ -390,7 +416,25 @@ function postSlide() {
390
416
  notes = notes.html();
391
417
  }
392
418
 
393
- $('#notes').html(notes);
419
+ $('#notes').html(notes);
420
+
421
+ var sections = getCurrentSections();
422
+ if(sections.size() > 1) {
423
+ var ul = $('<ul>').addClass('section-selector');
424
+ sections.each(function(idx, value){
425
+ var li = $('<li/>').appendTo(ul);
426
+ var a = $('<a/>')
427
+ .text(value)
428
+ .attr('href','javascript:setCurrentSection("'+value+'");')
429
+ .appendTo(li);
430
+
431
+ if(section == value) {
432
+ li.addClass('selected');
433
+ }
434
+ });
435
+
436
+ $('#notes').prepend(ul);
437
+ }
394
438
 
395
439
  if (notesWindow && typeof(notesWindow) != 'undefined' && !notesWindow.closed) {
396
440
  $(notesWindow.document.body).html(notes);
@@ -478,67 +522,106 @@ function presenterKeyDown(event){
478
522
 
479
523
  //* TIMER *//
480
524
 
481
- var timerSetUp = false;
482
- var timerRunning = false;
483
- var intervalRunning = false;
484
- var seconds = 0;
485
- var totalMinutes = 35;
525
+ var timerRunning = false;
526
+ var timerIntervals = [];
527
+
528
+ function startTimer() {
529
+ timerRunning = true;
530
+
531
+ $("#timerLabel").hide();
532
+ $("#minStart").hide();
533
+
534
+ $('#stopTimer').val('Cancel');
535
+ $("#stopTimer").show();
536
+ $("#pauseTimer").show();
537
+ $("#timerDisplay").show();
538
+ $("#timerSection").addClass('open');
539
+
540
+ var time = parseInt( $("#timerMinutes").val() ) * 60;
541
+ if(time) {
542
+ $('#timerDisplay')
543
+ .attr('data-timer', time)
544
+ .TimeCircles({
545
+ direction: 'Counter-clockwise',
546
+ total_duration: time,
547
+ count_past_zero: false,
548
+ time: {
549
+ Days: { show: false },
550
+ Hours: { show: false },
551
+ Seconds: { show: false },
552
+ }
553
+ }).addListener(timerProgress);
486
554
 
487
- function toggleTimer()
488
- {
489
- if (!timerRunning) {
490
- timerRunning = true
491
- totalMinutes = parseInt($("#timerMinutes").attr('value'))
492
- $("#minStart").hide()
493
- $("#minStop").show()
494
- $("#timerInfo").text(timerStatus(0));
495
- seconds = 0
496
- if (!intervalRunning) {
497
- intervalRunning = true
498
- setInterval(function() {
499
- if (!timerRunning) { return; }
500
- seconds++;
501
- $("#timerInfo").text(timerStatus(seconds));
502
- }, 1000); // fire every minute
555
+ // add 60 seconds to each interval because the timer works on floor()
556
+ timerIntervals = [ time/2+60, time/4+60, time/8+60, time/16+60 ]
557
+ }
558
+ }
559
+
560
+ function timerProgress(unit, value, total){
561
+
562
+ if (timerIntervals.length > 0) {
563
+ if (total < timerIntervals[0]) {
564
+
565
+ ts = $('#timerSection');
566
+
567
+ // clear all classes except for the one sizing the container
568
+ ts.attr('class', 'open');
569
+
570
+ // remove all the intervals we've already passed
571
+ timerIntervals = timerIntervals.filter(function(val) { return val < total });
572
+
573
+ switch(timerIntervals.length) {
574
+ case 3: ts.addClass('intervalHalf'); break;
575
+ case 2: ts.addClass('intervalQuarter'); break;
576
+ case 1: ts.addClass('intervalWarning'); break;
577
+ case 0:
578
+ ts.addClass('intervalCritical');
579
+ $("#timerDisplay").TimeCircles({circle_bg_color: "red"});
580
+
581
+ // when timing short durations, sometimes the last interval doesn't get triggered until we end.
582
+ if( $("#timerDisplay").TimeCircles().getTime() <= 0 ) {
583
+ endTimer();
584
+ }
585
+ break;
586
+ }
503
587
  }
504
- } else {
505
- seconds = 0
506
- timerRunning = false
507
- totalMinutes = 0
508
- setProgressColor(false)
509
- $("#timerInfo").text('')
510
- $("#minStart").show()
511
- $("#minStop").hide()
588
+ }
589
+ else {
590
+ endTimer();
591
+ }
592
+ }
593
+
594
+ function toggleTimer() {
595
+ if (!timerRunning) {
596
+ timerRunning = true;
597
+ $('#pauseTimer').val('Pause');
598
+ $('#timerDisplay').removeClass('paused');
599
+ $("#timerDisplay").TimeCircles().start();
600
+ }
601
+ else {
602
+ timerRunning = false;
603
+ $('#pauseTimer').val('Resume');
604
+ $('#timerDisplay').addClass('paused');
605
+ $("#timerDisplay").TimeCircles().stop();
512
606
  }
513
607
  }
514
608
 
515
- function timerStatus(seconds) {
516
- var minutes = Math.round(seconds / 60);
517
- var left = (totalMinutes - minutes);
518
- var percent = Math.round((minutes / totalMinutes) * 100);
519
- var progress = getSlidePercent() - percent;
520
- setProgressColor(progress);
521
- return minutes + '/' + left + ' - ' + percent + '%';
609
+ function endTimer() {
610
+ $('#stopTimer').val('Reset');
611
+ $("#pauseTimer").hide();
522
612
  }
523
613
 
524
- function setProgressColor(progress) {
525
- ts = $('#timerSection')
526
- ts.removeClass('tBlue')
527
- ts.removeClass('tGreen')
528
- ts.removeClass('tYellow')
529
- ts.removeClass('tRed')
614
+ function stopTimer() {
615
+ $("#timerDisplay").removeData('timer');
616
+ $("#timerDisplay").TimeCircles().destroy();
530
617
 
531
- if(progress === false) return;
618
+ $("#timerLabel").show();
619
+ $("#minStart").show();
532
620
 
533
- if(progress > 10) {
534
- ts.addClass('tBlue')
535
- } else if (progress > 0) {
536
- ts.addClass('tGreen')
537
- } else if (progress > -10) {
538
- ts.addClass('tYellow')
539
- } else {
540
- ts.addClass('tRed')
541
- }
621
+ $("#stopTimer").hide();
622
+ $("#pauseTimer").hide();
623
+ $("#timerDisplay").hide();
624
+ $('#timerSection').removeClass();
542
625
  }
543
626
 
544
627
  /********************
data/public/js/showoff.js CHANGED
@@ -16,15 +16,13 @@ var incrCode = false
16
16
  var debugMode = false
17
17
  var gotoSlidenum = 0
18
18
  var lastMessageGuid = 0
19
- var query
19
+ var query;
20
+ var section = 'handouts'; // default to showing handout notes for display view
20
21
  var slideStartTime = new Date().getTime()
21
22
 
22
23
  var loadSlidesBool
23
24
  var loadSlidesPrefix
24
25
 
25
- var keycode_dictionary,
26
- keycode_shifted_keys;
27
-
28
26
  var mode = { track: true, follow: true };
29
27
 
30
28
  $(document).on('click', 'code.execute', executeCode);
@@ -45,7 +43,6 @@ function setupPreso(load_slides, prefix) {
45
43
  loadSlidesPrefix = prefix || '/'
46
44
  loadSlides(loadSlidesBool, loadSlidesPrefix)
47
45
 
48
- loadKeyDictionaries();
49
46
  setupSideMenu();
50
47
 
51
48
  doDebugStuff()
@@ -54,10 +51,13 @@ function setupPreso(load_slides, prefix) {
54
51
  toggleKeybinding('on');
55
52
 
56
53
  $('#preso').addSwipeEvents().
57
- bind('tap', swipeLeft). // next
54
+ // bind('tap', swipeLeft). // next
58
55
  bind('swipeleft', swipeLeft). // next
59
56
  bind('swiperight', swipeRight); // prev
60
57
 
58
+ $('#buttonNav #buttonPrev').click(prevStep);
59
+ $('#buttonNav #buttonNext').click(nextStep);
60
+
61
61
  // give us the ability to disable tracking via url parameter
62
62
  if(query.track == 'false') mode.track = false;
63
63
 
@@ -96,13 +96,6 @@ function loadSlides(load_slides, prefix, reload) {
96
96
  }
97
97
  }
98
98
 
99
- function loadKeyDictionaries () {
100
- $.getJSON('js/keyDictionary.json', function(data) {
101
- keycode_dictionary = data['keycodeDictionary'];
102
- keycode_shifted_keys = data['shiftedKeyDictionary'];
103
- });
104
- }
105
-
106
99
  function initializePresentation(prefix) {
107
100
  // unhide for height to work in static mode
108
101
  $("#slides").show();
@@ -196,11 +189,11 @@ function setupSideMenu() {
196
189
  $("#hamburger").click(function() {
197
190
  $('#feedbackSidebar, #sidebarExit').toggle();
198
191
  toggleKeybinding();
199
-
200
192
  });
201
193
 
202
194
  $("#navToggle").click(function() {
203
- $("#navigation").toggle();
195
+ $("#navigation").toggle();
196
+ updateMenuChevrons();
204
197
  });
205
198
 
206
199
  $('#fileDownloads').click(function() {
@@ -217,19 +210,38 @@ function setupSideMenu() {
217
210
  });
218
211
 
219
212
  $('#questionToggle').click(function() {
220
- $('#questionSubmenu').toggle();
213
+ if ( ! $(this).hasClass('disabled') ) {
214
+ $('#questionSubmenu').toggle();
215
+ }
221
216
  });
222
217
  $("#askQuestion").click(function() {
223
- askQuestion( $("#question").val());
224
- feedback_response(this, "Sending...");
218
+ if ( ! $(this).hasClass('disabled') ) {
219
+ var question = $("#question").val()
220
+ var qid = askQuestion(question);
221
+
222
+ feedback_response(this, "Sending...");
223
+ $("#question").val('');
224
+
225
+ var questionItem = $('<li/>').text(question).attr('id', qid);
226
+ questionItem.click( function(e) {
227
+ cancelQuestion($(this).attr('id'));
228
+ $(this).remove();
229
+ });
230
+ $("#askedQuestions").append(questionItem);
231
+ }
225
232
  });
226
233
 
227
234
  $('#feedbackToggle').click(function() {
228
- $('#feedbackSubmenu').toggle();
235
+ if ( ! $(this).hasClass('disabled') ) {
236
+ $('#feedbackSubmenu').toggle();
237
+ }
229
238
  });
230
239
  $("#sendFeedback").click(function() {
231
- sendFeedback($( "input:radio[name=rating]:checked" ).val(), $("#feedback").val());
232
- feedback_response(this, "Sending...");
240
+ if ( ! $(this).hasClass('disabled') ) {
241
+ sendFeedback($( "input:radio[name=rating]:checked" ).val(), $("#feedback").val());
242
+ feedback_response(this, "Sending...");
243
+ $("#feedback").val('');
244
+ }
233
245
  });
234
246
 
235
247
  $("#editSlide").click(function() {
@@ -257,6 +269,28 @@ function setupSideMenu() {
257
269
  }
258
270
  }
259
271
 
272
+ function updateQuestionIndicator(count) {
273
+ if(count == 0) {
274
+ $('#questionsIndicator').hide();
275
+ }
276
+ else {
277
+ $('#questionsIndicator').show();
278
+ $('#questionsIndicator').text(count);
279
+ }
280
+ }
281
+
282
+ function updateMenuChevrons() {
283
+ $(".navSection + ul:not(:visible)")
284
+ .siblings('a')
285
+ .children('i')
286
+ .attr('class', 'fa fa-angle-down');
287
+
288
+ $(".navSection + ul:visible")
289
+ .siblings('a')
290
+ .children('i')
291
+ .attr('class', 'fa fa-angle-up');
292
+ }
293
+
260
294
  function setupMenu() {
261
295
  var nav = $("<ul>"),
262
296
  currentSection = '',
@@ -270,6 +304,7 @@ function setupMenu() {
270
304
  .shift();
271
305
  var headers = $(slide).children("h1, h2");
272
306
  var slideTitle = '';
307
+ var content;
273
308
 
274
309
  if (currentSection !== slidePath) {
275
310
  currentSection = slidePath;
@@ -283,6 +318,12 @@ function setupMenu() {
283
318
  .append(icon)
284
319
  .click(function() {
285
320
  $(this).next().toggle();
321
+ updateMenuChevrons();
322
+
323
+ if( $(this).parent().is(':last-child') ) {
324
+ $(this).next().children('li').first()[0].scrollIntoView();
325
+ }
326
+
286
327
  return false;
287
328
  });
288
329
  sectionUL = $("<ul>");
@@ -290,13 +331,18 @@ function setupMenu() {
290
331
  nav.append(newSection);
291
332
  }
292
333
 
334
+ // look for first header to use as a title
293
335
  if (headers.length > 0) {
294
336
  slideTitle = headers.first().text();
295
337
  } else {
296
- slideTitle = $(slide)
297
- .find(".content")
298
- .text()
299
- .substr(0, 20);
338
+ // if no header, look at content
339
+ content = $(slide).find(".content");
340
+ slideTitle = content.text().substr(0, 20).trim();
341
+
342
+ // if no content (like photo only) fall back to slide name
343
+ if (slideTitle == "") {
344
+ slideTitle = content.attr('ref').split('/').pop();
345
+ }
300
346
  }
301
347
 
302
348
  var navLink = $("<a>")
@@ -460,6 +506,11 @@ function showSlide(back_step, updatepv) {
460
506
  $(active).parent().addClass('highlighted');
461
507
  $(active).parent().parent().show();
462
508
 
509
+ updateMenuChevrons();
510
+
511
+ // copy notes to the notes field for mobile.
512
+ postSlide();
513
+
463
514
  return ret;
464
515
  }
465
516
 
@@ -468,9 +519,22 @@ function getSlideProgress()
468
519
  return (slidenum + 1) + '/' + slideTotal
469
520
  }
470
521
 
522
+ function getCurrentSections()
523
+ {
524
+ return currentSlide.find("div.notes-section").map(function() {
525
+ return $(this).attr('class').split(' ').filter(function(x) { return x != 'notes-section'; });
526
+ });
527
+ }
528
+
529
+ function setCurrentSection(newSection)
530
+ {
531
+ section = newSection;
532
+ postSlide();
533
+ }
534
+
471
535
  function getCurrentNotes()
472
536
  {
473
- var notes = currentSlide.find("div.notes");
537
+ var notes = currentSlide.find("div.notes-section."+section);
474
538
  return notes;
475
539
  }
476
540
 
@@ -583,50 +647,52 @@ function renderForm(form) {
583
647
  var action = form.attr("action");
584
648
  $.getJSON(action, function( data ) {
585
649
  //console.log(data);
586
- form.children('.element').each(function() {
587
- var key = $(this).attr('data-name');
650
+ form.children('.element').each(function(index, element) {
651
+ var key = $(element).attr('data-name');
588
652
 
589
653
  // add a counter label if we haven't already
590
- if( $(this).next('.count').length === 0 ) {
591
- $(this).after($('<h1>').addClass('count'));
654
+ if( $(element).next('.count').length === 0 ) {
655
+ $(element).after($('<h1>').addClass('count'));
592
656
  }
593
657
 
594
- $(this).find('ul > li > *').each(function() {
658
+ $(element).find('ul > li > *').each(function() {
595
659
  $(this).parent().parent().before(this);
596
660
  });
597
- $(this).children('ul').each(function() {
661
+ $(element).children('ul').each(function() {
598
662
  $(this).remove();
599
663
  });
600
664
 
601
- // replace all input widgets with spans for the bar chart
602
- $(this).children(':input').each(function() {
603
- switch( $(this).attr('type') ) {
665
+ // replace all input widgets with divs for the bar chart
666
+ $(element).children(':input').each(function(index, input) {
667
+ switch( $(input).attr('type') ) {
604
668
  case 'text':
605
669
  case 'button':
606
670
  case 'submit':
607
671
  case 'textarea':
608
672
  // we don't render these
609
- $(this).parent().remove();
673
+ $(input).parent().remove();
610
674
  break;
611
675
 
612
676
  case 'radio':
613
677
  case 'checkbox':
614
678
  // Just render these directly and migrate the label to inside the span
615
- var label = $(this).next('label');
679
+ var label = $(input).next('label');
616
680
  var text = label.text();
617
- var classes = $(this).attr('class');
681
+ var classes = $(input).attr('class');
618
682
 
619
683
  if(text.match(/^-+$/)) {
620
- $(this).remove();
684
+ $(input).remove();
621
685
  } else {
622
686
  var resultDiv = $('<div>')
623
687
  .addClass('item')
624
- .attr('data-value', $(this).attr('value'))
625
- .text(text);
688
+ .attr('data-value', $(input).attr('value'))
689
+ .append($('<span>').addClass('answer').text(text))
690
+ .append($('<div>').addClass('bar'));
691
+
626
692
  if (classes) {
627
693
  resultDiv.addClass(classes);
628
694
  }
629
- $(this).replaceWith(resultDiv);
695
+ $(input).replaceWith(resultDiv);
630
696
  }
631
697
  label.remove();
632
698
  break;
@@ -634,24 +700,25 @@ function renderForm(form) {
634
700
  default:
635
701
  // select doesn't have a type attribute... yay html
636
702
  // poke inside to get options, then render each as a span and replace the select
637
- var parent = $(this).parent();
703
+ var parent = $(input).parent();
638
704
 
639
- $(this).children('option').each(function() {
640
- var text = $(this).text();
641
- var classes = $(this).attr('class');
705
+ $(input).children('option').each(function() {
706
+ var text = $(input).text();
707
+ var classes = $(input).attr('class');
642
708
 
643
709
  if(! text.match(/^-+$/)) {
644
710
  var resultDiv = $('<div>')
645
711
  .addClass('item')
646
- .attr('data-value', $(this).val())
647
- .text(text);
712
+ .attr('data-value', $(input).val())
713
+ .append($('<span>').addClass('answer').text(text))
714
+ .append($('<div>').addClass('bar'));
648
715
  if (classes) {
649
716
  resultDiv.addClass(classes);
650
717
  }
651
718
  parent.append(resultDiv);
652
719
  }
653
720
  });
654
- $(this).remove();
721
+ $(input).remove();
655
722
  break;
656
723
  }
657
724
  });
@@ -661,8 +728,8 @@ function renderForm(form) {
661
728
  // number of unique responses
662
729
  var total = 0;
663
730
  // double loop so we can handle re-renderings of the form
664
- $(this).find('.item').each(function() {
665
- var name = $(this).attr('data-value');
731
+ $(element).find('.item').each(function(index, item) {
732
+ var name = $(item).attr('data-value');
666
733
 
667
734
  if(key in data) {
668
735
  var count = data[key]['responses'][name];
@@ -672,12 +739,12 @@ function renderForm(form) {
672
739
  });
673
740
 
674
741
  // insert the total into the counter label
675
- $(this).next('.count').each(function() {
676
- $(this).text(total);
742
+ $(element).next('.count').each(function(index, icount) {
743
+ $(icount).text(total);
677
744
  });
678
745
 
679
- var oldTotal = $(this).attr('data-total');
680
- $(this).find('.item').each(function() {
746
+ var oldTotal = $(element).attr('data-total');
747
+ $(element).find('.item').each(function() {
681
748
  var name = $(this).attr('data-value');
682
749
  var oldCount = $(this).attr('data-count');
683
750
 
@@ -689,29 +756,35 @@ function renderForm(form) {
689
756
  }
690
757
 
691
758
  if(count != oldCount || total != oldTotal) {
692
- var percent = (total) ? ((count/total)*100)+'%' : '0%';
759
+ var percent = (total) ? ((count/total)*100) + '%' : '0%';
693
760
 
694
761
  $(this).attr('data-count', count);
695
- $(this).animate({width: percent});
762
+ $(this).find('.bar').animate({width: percent});
696
763
  }
697
764
  });
698
765
 
699
766
  // record the old total value so we only animate when it changes
700
- $(this).attr('data-total', total);
767
+ $(element).attr('data-total', total);
701
768
  }
702
769
 
703
- $(this).addClass('rendered');
770
+ $(element).addClass('rendered');
704
771
  });
705
772
 
706
773
  });
707
774
  }
708
775
 
709
776
  function connectControlChannel() {
710
- protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
711
- ws = new WebSocket(protocol + location.host + '/control');
712
- ws.onopen = function() { connected(); };
713
- ws.onclose = function() { disconnected(); }
714
- ws.onmessage = function(m) { parseMessage(m.data); };
777
+ if (interactive) {
778
+ protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
779
+ ws = new WebSocket(protocol + location.host + '/control');
780
+ ws.onopen = function() { connected(); };
781
+ ws.onclose = function() { disconnected(); }
782
+ ws.onmessage = function(m) { parseMessage(m.data); };
783
+ }
784
+ else {
785
+ ws = {}
786
+ ws.send = function() { /* no-op */ }
787
+ }
715
788
  }
716
789
 
717
790
  // This exists as an intermediary simply so the presenter view can override it
@@ -721,12 +794,11 @@ function reconnectControlChannel() {
721
794
 
722
795
  function connected() {
723
796
  console.log('Control socket opened');
724
- $("#feedbackSidebar button").attr("disabled", false);
797
+ $("#feedbackSidebar .interactive").removeClass("disabled");
725
798
  $("img#disconnected").hide();
726
799
 
727
800
  try {
728
- // If we are a presenter, then remind the server where we are
729
- update();
801
+ // If we are a presenter, then remind the server who we are
730
802
  register();
731
803
  }
732
804
  catch (e) {}
@@ -734,12 +806,24 @@ function connected() {
734
806
 
735
807
  function disconnected() {
736
808
  console.log('Control socket closed');
737
- $("#feedbackSidebar button").attr("disabled", true);
809
+ $("#feedbackSidebar .interactive").addClass("disabled");
738
810
  $("img#disconnected").show();
739
811
 
740
812
  setTimeout(function() { reconnectControlChannel() } , 5000);
741
813
  }
742
814
 
815
+ function generateGuid() {
816
+ var result, i, j;
817
+ result = 'S';
818
+ for(j=0; j<32; j++) {
819
+ if( j == 8 || j == 12|| j == 16|| j == 20)
820
+ result = result + '-';
821
+ i = Math.floor(Math.random()*16).toString(16).toUpperCase();
822
+ result = result + i;
823
+ }
824
+ return result;
825
+ }
826
+
743
827
  function parseMessage(data) {
744
828
  var command = JSON.parse(data);
745
829
 
@@ -753,42 +837,78 @@ function parseMessage(data) {
753
837
  }
754
838
  }
755
839
 
756
- if ("current" in command) { follow(command["current"]); }
757
-
758
- // Presenter messages only, so catch errors if method doesn't exist
759
840
  try {
760
- if ("pace" in command) { paceFeedback(command["pace"]); }
761
- if ("question" in command) { askQuestion(command["question"]); }
841
+ switch (command['message']) {
842
+ case 'current':
843
+ follow(command["current"]);
844
+ break;
845
+
846
+ case 'complete':
847
+ completeQuestion(command["questionID"]);
848
+ break;
849
+
850
+ case 'pace':
851
+ paceFeedback(command["pace"]);
852
+ break;
853
+
854
+ case 'question':
855
+ postQuestion(command["question"], command["questionID"]);
856
+ break;
857
+
858
+ case 'cancel':
859
+ removeQuestion(command["questionID"]);
860
+ break;
861
+ }
762
862
  }
763
863
  catch(e) {
764
864
  console.log("Not a presenter!");
765
865
  }
866
+
766
867
  }
767
868
 
768
869
  function sendPace(pace) {
769
- ws.send(JSON.stringify({ message: 'pace', pace: pace}));
770
- feedbackActivity();
870
+ if (ws.readyState == WebSocket.OPEN) {
871
+ ws.send(JSON.stringify({ message: 'pace', pace: pace}));
872
+ }
771
873
  }
772
874
 
773
875
  function askQuestion(question) {
774
- ws.send(JSON.stringify({ message: 'question', question: question}));
775
- feedbackActivity();
876
+ if (ws.readyState == WebSocket.OPEN) {
877
+ var questionID = generateGuid();
878
+ ws.send(JSON.stringify({ message: 'question', question: question, questionID: questionID}));
879
+ return questionID;
880
+ }
881
+ }
882
+
883
+ function cancelQuestion(questionID) {
884
+ if (ws.readyState == WebSocket.OPEN) {
885
+ ws.send(JSON.stringify({ message: 'cancel', questionID: questionID}));
886
+ }
887
+ }
888
+
889
+ function completeQuestion(questionID) {
890
+ var question = $("li#"+questionID)
891
+ if(question.length > 0) {
892
+ question.addClass('closed');
893
+ feedbackActivity();
894
+ }
776
895
  }
777
896
 
778
897
  function sendFeedback(rating, feedback) {
779
- var slide = $("#slideFilename").text();
780
- ws.send(JSON.stringify({ message: 'feedback', rating: rating, feedback: feedback, slide: slide}));
781
- $("input:radio[name=rating]:checked").attr('checked', false);
782
- feedbackActivity();
898
+ if (ws.readyState == WebSocket.OPEN) {
899
+ var slide = $("#slideFilename").text();
900
+ ws.send(JSON.stringify({ message: 'feedback', rating: rating, feedback: feedback, slide: slide}));
901
+ $("input:radio[name=rating]:checked").attr('checked', false);
902
+ }
783
903
  }
784
904
 
785
905
  function feedbackActivity() {
786
- $("img#feedbackActivity").show();
787
- setTimeout(function() { $("img#feedbackActivity").hide() }, 1000);
906
+ $('#hamburger').addClass('highlight');
907
+ setTimeout(function() { $("#hamburger").removeClass('highlight') }, 75);
788
908
  }
789
909
 
790
910
  function track() {
791
- if (mode.track) {
911
+ if (mode.track && ws.readyState == WebSocket.OPEN) {
792
912
  var slideName = $("#slideFilename").text();
793
913
  var slideEndTime = new Date().getTime();
794
914
  var elapsedTime = slideEndTime - slideStartTime;
@@ -859,6 +979,23 @@ function nextStep(updatepv)
859
979
  }
860
980
  }
861
981
 
982
+ // carrying on our grand tradition of overwriting functions of the same name with presenter.js
983
+ function postSlide() {
984
+ if(currentSlide) {
985
+ var notes = getCurrentNotes();
986
+ // Replace notes with empty string if there are no notes
987
+ // Otherwise it fails silently and does not remove old notes
988
+ if (notes.length === 0) {
989
+ notes = "";
990
+ } else {
991
+ notes = notes.html();
992
+ }
993
+
994
+ $('#notes').html(notes);
995
+ }
996
+ }
997
+
998
+
862
999
  function doDebugStuff()
863
1000
  {
864
1001
  if (debugMode) {
@@ -1025,7 +1162,9 @@ function toggleHelp () {
1025
1162
  }
1026
1163
 
1027
1164
  function toggleContents () {
1028
- $('#navmenu').toggle().trigger('click');
1165
+ $('#feedbackSidebar, #sidebarExit').toggle();
1166
+ $("#navigation").toggle();
1167
+ updateMenuChevrons();
1029
1168
  }
1030
1169
 
1031
1170
  function swipeLeft() {