showoff 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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() {