showoff 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bcf39224213729d49b49c882caac09ee531ad443
4
- data.tar.gz: 61ad24fcd9c449045ef49bb9b926483bf67c1b2c
3
+ metadata.gz: b168170069c1f28da7c3fe26bff21344cf8ad6bd
4
+ data.tar.gz: b42c67ff2736fbeda95819f6c3d61b4a1ea2980b
5
5
  SHA512:
6
- metadata.gz: 67824aa75b98a4397a7829faa7fe8926a32c4cbe6dcfed121812076e3951b9871dab6ae13c5ec8f661d9c248fe02bb501c0be9d8ce87385b3342d282ba0501ee
7
- data.tar.gz: 0f39a3db0cc58ed01d20fad3316ea303562e93ab6f917b30bdd29f0e1781524aa13270f9e935daaa7cd9f1d38aa41ce561edbb72401c3056bb052b2f8c201d58
6
+ metadata.gz: 7056bb877d7f2066f713d31e6e32c58bb6661c52c650f89aa45dc415ef5e96dbf4ff9273746b8373c8c552e9e5290229c60c6fbcefb500da5bd7c13a662b9ba2
7
+ data.tar.gz: ae64ba89fa60b8a14f64f2cc5392fedf0e26dfc60aa2eaea74c4496d5a52f63fa4f43d5f06a83d42bd43a4abe79ecdf35ad2daaa025f372b2ab5ea0b95628746
data/bin/showoff CHANGED
@@ -117,25 +117,37 @@ default_value "."
117
117
  command :serve do |c|
118
118
 
119
119
  c.desc 'Show verbose messaging'
120
- c.switch :verbose
120
+ c.switch [:v, :verbose]
121
121
 
122
122
  c.desc 'Enable code review'
123
- c.switch :review
123
+ c.switch [:r, :review]
124
124
 
125
125
  c.desc 'Enable remote code execution'
126
- c.switch [:x,:executecode]
126
+ c.switch [:x, :executecode]
127
+
128
+ c.desc 'Disable content caching'
129
+ c.switch :nocache
127
130
 
128
131
  c.desc 'Port on which to run'
129
132
  c.default_value "9090"
130
- c.flag [:p,:port]
133
+ c.flag [:p, :port]
131
134
 
132
135
  c.desc 'Host or ip to run on'
133
136
  c.default_value "0.0.0.0"
134
- c.flag [:h,:host]
137
+ c.flag [:h, :host]
138
+
139
+ c.desc 'Run via HTTPS'
140
+ c.switch [:s, :ssl]
141
+
142
+ c.desc 'Path to SSL certificate. Showoff will generate one automatically if unset.'
143
+ c.flag :ssl_certificate
144
+
145
+ c.desc 'Path to SSL private key. Showoff will generate one automatically if unset.'
146
+ c.flag :ssl_private_key
135
147
 
136
148
  c.desc 'JSON file used to describe presentation'
137
149
  c.default_value "showoff.json"
138
- c.flag [:f, :pres_file]
150
+ c.flag [:f, :file, :pres_file]
139
151
 
140
152
  c.action do |global_options,options,args|
141
153
 
@@ -145,8 +157,15 @@ command :serve do |c|
145
157
  raise "This presentation requires Showoff version #{config['version']} or greater."
146
158
  end
147
159
 
148
- host = options[:h] == '0.0.0.0' ? 'localhost' : options[:h]
149
- url = "http://#{host}:#{options[:p].to_i}"
160
+ options[:host] ||= config['host']
161
+ options[:port] ||= config['port']
162
+ options[:ssl] ||= config['ssl']
163
+ options[:ssl_certificate] ||= config['ssl_certificate']
164
+ options[:ssl_private_key] ||= config['ssl_private_key']
165
+
166
+ protocol = options[:ssl] ? 'https' : 'http'
167
+ host = options[:host] == '0.0.0.0' ? 'localhost' : options[:host]
168
+ url = "#{protocol}://#{host}:#{options[:p].to_i}"
150
169
  puts "
151
170
  -------------------------
152
171
 
@@ -160,14 +179,28 @@ To run it from presenter view, go to: [ #{url}/presenter ]
160
179
 
161
180
  "
162
181
 
163
- ShowOff.run! :host => options[:h],
164
- :port => options[:p].to_i,
165
- :pres_file => options[:f],
182
+ ShowOff.run!( :host => options[:host],
183
+ :port => options[:port].to_i,
184
+ :pres_file => options[:file],
166
185
  :pres_dir => args[0],
167
186
  :verbose => options[:verbose],
168
187
  :review => options[:review],
169
- :execute => options[:x],
170
- :bind => options[:h]
188
+ :execute => options[:executecode],
189
+ :nocache => options[:nocache],
190
+ :bind => options[:host],
191
+ ) do |server|
192
+ if options[:ssl]
193
+ ssl_options = {
194
+ :cert_chain_file => options[:ssl_certificate],
195
+ :private_key_file => options[:ssl_private_key],
196
+ :verify_peer => false,
197
+ }
198
+
199
+ server.ssl = true
200
+ server.ssl_options = ssl_options
201
+ end
202
+ end
203
+
171
204
  end
172
205
  end
173
206
 
@@ -1,3 +1,3 @@
1
1
  # No namespace here since ShowOff is a class and I'd have to inherit from
2
2
  # Sinatra::Application (which we don't want to load here)
3
- SHOWOFF_VERSION = '0.10.2'
3
+ SHOWOFF_VERSION = '0.11.0'
data/lib/showoff.rb CHANGED
@@ -13,7 +13,7 @@ require "#{here}/commandline_parser"
13
13
  require "#{here}/keymap"
14
14
 
15
15
  begin
16
- require 'RMagick'
16
+ require 'rmagick'
17
17
  rescue LoadError
18
18
  # nop
19
19
  end
@@ -121,8 +121,13 @@ class ShowOff < Sinatra::Application
121
121
  # Page view time accumulator. Tracks how often slides are viewed by the audience
122
122
  begin
123
123
  @@counter = JSON.parse(File.read("#{settings.statsdir}/#{settings.viewstats}"))
124
+
125
+ # port old format stats
126
+ unless @@counter.has_key? 'user_agents'
127
+ @@counter = { 'user_agents' => {}, 'pageviews' => @@counter }
128
+ end
124
129
  rescue
125
- @@counter = Hash.new
130
+ @@counter = { 'user_agents' => {}, 'pageviews' => {} }
126
131
  end
127
132
 
128
133
  # keeps track of form responses. In memory to avoid concurrence issues.
@@ -135,6 +140,7 @@ class ShowOff < Sinatra::Application
135
140
  @@downloads = Hash.new # Track downloadable files
136
141
  @@cookie = nil # presenter cookie. Identifies the presenter for control messages
137
142
  @@current = Hash.new # The current slide that the presenter is viewing
143
+ @@cache = nil # Cache slide content for subsequent hits
138
144
 
139
145
  # flush stats to disk periodically
140
146
  Thread.new do
@@ -458,9 +464,9 @@ class ShowOff < Sinatra::Application
458
464
  end
459
465
  end
460
466
 
461
- # Now add a target so we open all links from notes in a new window
467
+ # Now add a target so we open all external links from notes in a new window
462
468
  doc.css('a').each do |link|
463
- link.set_attribute('target', '_blank')
469
+ link.set_attribute('target', '_blank') unless link['href'].start_with? '#'
464
470
  end
465
471
 
466
472
  doc.to_html
@@ -513,7 +519,7 @@ class ShowOff < Sinatra::Application
513
519
  form = "<form id='#{title}' action='/form/#{title}' method='POST'>#{content}#{tools}</form>"
514
520
  doc = Nokogiri::HTML::DocumentFragment.parse(form)
515
521
  doc.css('p').each do |p|
516
- if p.text =~ /^(\w*) ?(?:->)? ?([^\*]*)? ?(\*?)= ?(.*)?$/
522
+ if p.text =~ /^(\w*) ?(?:->)? ?(.*)? (\*?)= ?(.*)?$/
517
523
  code = $1
518
524
  id = "#{title}_#{code}"
519
525
  name = $2.empty? ? code : $2
@@ -536,19 +542,19 @@ class ShowOff < Sinatra::Application
536
542
  str = "<div class='form element #{required}' id='#{id}' data-name='#{code}'>"
537
543
  str << "<label for='#{id}'>#{name}</label>"
538
544
  case rhs
539
- when /^\[\s+(\d*)\]$$/ # value = [ 5] (textarea)
545
+ when /^\[\s+(\d*)\]$$/ # value = [ 5] (textarea)
540
546
  str << form_element_textarea(id, code, $1)
541
- when /^___+(?:\[(\d+)\])?$/ # value = ___[50] (text)
547
+ when /^___+(?:\[(\d+)\])?$/ # value = ___[50] (text)
542
548
  str << form_element_text(id, code, $1)
543
- when /^\(x?\)/ # value = (x) option one () opt2 () opt3 -> option 3 (radio)
544
- str << form_element_radio(id, code, rhs.scan(/\((x?)\)\s*([^()]+)\s*/))
545
- when /^\[x?\]/ # value = [x] option one [] opt2 [] opt3 -> option 3 (checkboxes)
546
- str << form_element_checkboxes(id, code, rhs.scan(/\[(x?)\] ?([^\[\]]+)/))
547
- when /^\{(.*)\}$/ # value = {BOS, SFO, (NYC)} (select shorthand)
548
- str << form_element_select(id, code, rhs.scan(/\(?\w+\)?/))
549
- when /^\{$/ # value = { (select)
549
+ when /^\(.?\)/ # value = (x) option one (=) opt2 () opt3 -> option 3 (radio)
550
+ str << form_element_radio(id, code, rhs.scan(/\((.?)\)\s*([^()]+)\s*/))
551
+ when /^\[.?\]/ # value = [x] option one [=] opt2 [] opt3 -> option 3 (checkboxes)
552
+ str << form_element_checkboxes(id, code, rhs.scan(/\[(.?)\] ?([^\[\]]+)/))
553
+ when /^\{(.*)\}$/ # value = {BOS, [SFO], (NYC)} (select shorthand)
554
+ str << form_element_select(id, code, rhs.scan(/[(\[]?\w+[)\]]?/))
555
+ when /^\{$/ # value = { (select)
550
556
  str << form_element_select_multiline(id, code, text)
551
- when '' # value = (radio/checkbox list)
557
+ when '' # value = (radio/checkbox list)
552
558
  str << form_element_multiline(id, code, text)
553
559
  else
554
560
  @logger.warn "Unmatched form element: #{rhs}"
@@ -597,9 +603,13 @@ class ShowOff < Sinatra::Application
597
603
  case item
598
604
  when /^ +\((\w+) -> (.+)\),?$/ # (NYC -> New York City)
599
605
  str << "<option value='#{$1}' selected>#{$2}</option>"
606
+ when /^ +\[(\w+) -> (.+)\],?$/ # [NYC -> New York City]
607
+ str << "<option value='#{$1}' class='correct'>#{$2}</option>"
600
608
  when /^ +(\w+) -> (.+),?$/ # NYC -> New, York City
601
609
  str << "<option value='#{$1}'>#{$2}</option>"
602
- when /^ +\((.+)[^,],?$/ # (Boston)
610
+ when /^ +\((.+)\)$/ # (Boston)
611
+ str << "<option value='#{$1}' selected>#{$1}</option>"
612
+ when /^ +\[(.+)\]$/ # [Boston]
603
613
  str << "<option value='#{$1}' selected>#{$1}</option>"
604
614
  when /^ +([^\(].+[^\),]),?$/ # Boston
605
615
  str << "<option value='#{$1}'>#{$1}</option>"
@@ -613,20 +623,20 @@ class ShowOff < Sinatra::Application
613
623
 
614
624
  text.split("\n")[1..-1].each do |item|
615
625
  case item
616
- when /\((x?)\)\s*(\w+)\s*(?:->\s*(.*)?)?/
617
- checked = $1.empty? ? '': "checked='checked'"
618
- type = 'radio'
619
- value = $2
620
- label = $3 || $2
621
- when /\[(x?)\]\s*(\w+)\s*(?:->\s*(.*)?)?/
622
- checked = $1.empty? ? '': "checked='checked'"
623
- type = 'checkbox'
624
- value = $2
625
- label = $3 || $2
626
+ when /\((.?)\)\s*(\w+)\s*(?:->\s*(.*)?)?/
627
+ modifier = $1
628
+ type = 'radio'
629
+ value = $2
630
+ label = $3 || $2
631
+ when /\[(.?)\]\s*(\w+)\s*(?:->\s*(.*)?)?/
632
+ modifier = $1
633
+ type = 'checkbox'
634
+ value = $2
635
+ label = $3 || $2
626
636
  end
627
637
 
628
638
  str << '<li>'
629
- str << form_element_check_or_radio(type, id, code, value, label, checked)
639
+ str << form_element_check_or_radio(type, id, code, value, label, modifier)
630
640
  str << '</li>'
631
641
  end
632
642
  str << '</ul>'
@@ -635,7 +645,7 @@ class ShowOff < Sinatra::Application
635
645
  def form_element_check_or_radio_set(type, id, code, items)
636
646
  str = ''
637
647
  items.each do |item|
638
- checked = item[0].empty? ? '': "checked='checked'"
648
+ modifier = item[0]
639
649
 
640
650
  if item[1] =~ /^(\w*) -> (.*)$/
641
651
  value = $1
@@ -644,17 +654,31 @@ class ShowOff < Sinatra::Application
644
654
  value = label = item[1]
645
655
  end
646
656
 
647
- str << form_element_check_or_radio(type, id, code, value, label, checked)
657
+ str << form_element_check_or_radio(type, id, code, value, label, modifier)
648
658
  end
649
659
  str
650
660
  end
651
661
 
652
- def form_element_check_or_radio(type, id, code, value, label, checked)
662
+ def form_element_check_or_radio(type, id, code, value, label, modifier)
653
663
  # yes, value and id are conflated, because this is the id of the parent widget
664
+ checked = form_checked?(modifier)
665
+ classes = form_classes(modifier)
654
666
 
655
667
  name = (type == 'checkbox') ? "#{code}[]" : code
656
- str = "<input type='#{type}' name='#{name}' id='#{id}_#{value}' value='#{value}' #{checked} />"
657
- str << "<label for='#{id}_#{value}'>#{label}</label>"
668
+ str = "<input type='#{type}' name='#{name}' id='#{id}_#{value}' value='#{value}' class='#{classes}' #{checked} />"
669
+ str << "<label for='#{id}_#{value}' class='#{classes}'>#{label}</label>"
670
+ end
671
+
672
+ def form_classes(modifier)
673
+ modifier.downcase!
674
+ classes = []
675
+ classes << 'correct' if modifier.include?('=')
676
+
677
+ classes.join
678
+ end
679
+
680
+ def form_checked?(modifier)
681
+ modifier.downcase.include?('x') ? "checked='checked'" : ''
658
682
  end
659
683
 
660
684
  # TODO: deprecated
@@ -931,7 +955,13 @@ class ShowOff < Sinatra::Application
931
955
  end
932
956
 
933
957
  def slides(static=false)
934
- get_slides_html(:static=>static)
958
+ # if we have a cache and we're not asking to invalidate it
959
+ return @@cache if (@@cache and params['cache'] != 'clear')
960
+ content = get_slides_html(:static=>static)
961
+
962
+ # allow command line cache disabling
963
+ @@cache = content unless settings.nocache
964
+ content
935
965
  end
936
966
 
937
967
  def onepage(static=false)
@@ -970,12 +1000,12 @@ class ShowOff < Sinatra::Application
970
1000
 
971
1001
  def stats()
972
1002
  if request.env['REMOTE_HOST'] == 'localhost'
973
- # the presenter should have full stats
974
- @counter = @@counter
1003
+ # the presenter should have full stats in the erb
1004
+ @counter = @@counter['pageviews']
975
1005
  end
976
1006
 
977
1007
  @all = Hash.new
978
- @@counter.each do |slide, stats|
1008
+ @@counter['pageviews'].each do |slide, stats|
979
1009
  @all[slide] = 0
980
1010
  stats.map do |host, visits|
981
1011
  visits.each { |entry| @all[slide] += entry['elapsed'].to_f }
@@ -1158,16 +1188,21 @@ class ShowOff < Sinatra::Application
1158
1188
 
1159
1189
  @@forms[id].each_with_object({}) do |(ip,form), sum|
1160
1190
  form.each do |key, val|
1161
- sum[key] ||= {}
1191
+ # initialize the object with an empty response if needed
1192
+ sum[key] ||= { 'count' => 0, 'responses' => {} }
1193
+
1194
+ # increment the number of unique responses we've seen
1195
+ sum[key]['count'] += 1
1162
1196
 
1197
+ responses = sum[key]['responses']
1163
1198
  if val.class == Array
1164
1199
  val.each do |item|
1165
- sum[key][item] ||= 0
1166
- sum[key][item] += 1
1200
+ responses[item] ||= 0
1201
+ responses[item] += 1
1167
1202
  end
1168
1203
  else
1169
- sum[key][val] ||= 0
1170
- sum[key][val] += 1
1204
+ responses[val] ||= 0
1205
+ responses[val] += 1
1171
1206
  end
1172
1207
  end
1173
1208
  end.to_json
@@ -1294,14 +1329,18 @@ class ShowOff < Sinatra::Application
1294
1329
  slide = control['slide']
1295
1330
  time = control['time'].to_f
1296
1331
 
1297
- @logger.debug "Logged #{time} on slide #{slide} for #{remote}"
1332
+ # record the UA of the client if we haven't seen it before
1333
+ @@counter['user_agents'][remote] ||= request.user_agent
1298
1334
 
1335
+ views = @@counter['pageviews']
1299
1336
  # a bucket for this slide
1300
- @@counter[slide] ||= Hash.new
1337
+ views[slide] ||= Hash.new
1301
1338
  # a bucket of slideviews for this address
1302
- @@counter[slide][remote] ||= Array.new
1339
+ views[slide][remote] ||= Array.new
1303
1340
  # and add this slide viewing to the bucket
1304
- @@counter[slide][remote] << { 'elapsed' => time, 'timestamp' => Time.now.to_i, 'presenter' => @@current[:name] }
1341
+ views[slide][remote] << { 'elapsed' => time, 'timestamp' => Time.now.to_i, 'presenter' => @@current[:name] }
1342
+
1343
+ @logger.debug "Logged #{time} on slide #{slide} for #{remote}"
1305
1344
 
1306
1345
  when 'position'
1307
1346
  ws.send( { 'current' => @@current[:number] }.to_json ) unless @@cookie.nil?
@@ -175,6 +175,12 @@ div.zoomed {
175
175
  background-color: #fff;
176
176
  font-family: helvetica;
177
177
  }
178
+
179
+ .menu ul {
180
+ list-style: none;
181
+ padding: 0;
182
+ }
183
+
178
184
  .menu ul li {
179
185
  padding: 5px;
180
186
  }
@@ -234,6 +240,11 @@ div.zoomed {
234
240
  display: none;
235
241
  }
236
242
 
243
+ #preview .content form label.correct {
244
+ font-weight: 900;
245
+ border-bottom: 4px solid black;
246
+ }
247
+
237
248
  img#disconnected {
238
249
  margin: 0.5em 1em;
239
250
  }
@@ -349,6 +360,19 @@ div.zoomed {
349
360
  list-style-type: disc;
350
361
  }
351
362
 
363
+ #notes div.rendered.form span.count {
364
+ display: inline;
365
+ position: absolute;
366
+ top: 3px;
367
+ right: 4px;
368
+ z-index: 10;
369
+ font-size: 2.5em;
370
+ background-color: #ddd;
371
+ border: 1px solid #333;
372
+ border-radius: 0 0.15em 0 0;
373
+ box-shadow: 2px 2px 2px #888;
374
+ }
375
+
352
376
  #bottom #notes div.personal {
353
377
  float: right;
354
378
  border-left: 4px solid #999;
@@ -641,6 +641,11 @@ div.rendered.form {
641
641
  border-radius: 0.5em;
642
642
  margin: 1em;
643
643
  padding: 0.25em;
644
+ min-height: 3em;
645
+ }
646
+ div.rendered.form.element {
647
+ position: relative; /* for absolutely positioning children */
648
+ padding-left: 0.5em;
644
649
  }
645
650
  div.rendered.form label {
646
651
  display: block;
@@ -653,11 +658,23 @@ div.rendered.form .item {
653
658
  /* overflow: hidden; */
654
659
  height: 1.25em;
655
660
  white-space: nowrap;
661
+ opacity: 0.5;
662
+ border-radius: 0 0.5em 0.5em 0;
663
+ margin: 2px 0;
664
+ }
665
+ div.rendered.form .item.correct {
666
+ font-weight: 900;
667
+ border-left: 4px solid black;
668
+ opacity: 1.0;
656
669
  }
670
+ div.rendered.form span.count {
671
+ display: none;
672
+ }
673
+
657
674
  div.rendered.form .item.barstyle0 { background-color: #bb73bb; }
658
675
  div.rendered.form .item.barstyle1 { background-color: #59b859; }
659
676
  div.rendered.form .item.barstyle2 { background-color: #e3742f; }
660
- div.rendered.form .item.barstyle3 { background-color: #4848e8; }
677
+ div.rendered.form .item.barstyle3 { background-color: #48a2ee; }
661
678
  div.rendered.form .item.barstyle4 { background-color: #f75d5d; }
662
679
 
663
680
  #notes .form.wrapper {
data/public/js/showoff.js CHANGED
@@ -102,10 +102,14 @@ function setupPreso(load_slides, prefix) {
102
102
  */
103
103
  }
104
104
 
105
- function loadSlides(load_slides, prefix) {
105
+ function loadSlides(load_slides, prefix, reload) {
106
+ var url = loadSlidesPrefix + "slides";
107
+ if (reload) {
108
+ url += "?cache=clear";
109
+ }
106
110
  //load slides offscreen, wait for images and then initialize
107
111
  if (load_slides) {
108
- $("#slides").load(loadSlidesPrefix + "slides", false, function(){
112
+ $("#slides").load(url, false, function(){
109
113
  $("#slides img").batchImageLoad({
110
114
  loadingCompleteCallback: initializePresentation(prefix)
111
115
  })
@@ -118,7 +122,7 @@ function loadSlides(load_slides, prefix) {
118
122
  }
119
123
 
120
124
  function loadKeyDictionaries () {
121
- $.getJSON('/js/keyDictionary.json', function(data) {
125
+ $.getJSON('js/keyDictionary.json', function(data) {
122
126
  keycode_dictionary = data['keycodeDictionary'];
123
127
  keycode_shifted_keys = data['shiftedKeyDictionary'];
124
128
  });
@@ -504,7 +508,11 @@ function renderForm(form) {
504
508
  //console.log(data);
505
509
  form.children('div.form.element').each(function() {
506
510
  var key = $(this).attr('data-name');
507
- var sum = 0;
511
+
512
+ // add a counter label if we haven't already
513
+ if( $(this).has('span.count').length == 0 ) {
514
+ $(this).prepend('<span class="count"></span>');
515
+ }
508
516
 
509
517
  $(this).find('ul > li > *').each(function() {
510
518
  $(this).parent().parent().before(this);
@@ -529,15 +537,16 @@ function renderForm(form) {
529
537
  case 'radio':
530
538
  case 'checkbox':
531
539
  // Just render these directly and migrate the label to inside the span
532
- var value = $(this).attr('value');
533
- var label = $(this).next('label');
534
- var text = label.text();
540
+ var value = $(this).attr('value');
541
+ var label = $(this).next('label');
542
+ var classes = $(this).attr('class');
543
+ var text = label.text();
535
544
 
536
545
  if(text.match(/^-+$/)) {
537
546
  $(this).remove();
538
547
  }
539
548
  else{
540
- $(this).replaceWith('<div class="item barstyle'+style+'" data-value="'+value+'">'+text+'</div>');
549
+ $(this).replaceWith('<div class="item barstyle'+style+' '+classes+'" data-value="'+value+'">'+text+'</div>');
541
550
  }
542
551
  label.remove();
543
552
  break;
@@ -548,11 +557,12 @@ function renderForm(form) {
548
557
  parent = $(this).parent();
549
558
 
550
559
  $(this).children('option').each(function() {
551
- var value = $(this).val();
552
- var text = $(this).text();
560
+ var value = $(this).val();
561
+ var text = $(this).text();
562
+ var classes = $(this).attr('class');
553
563
 
554
564
  if(! text.match(/^-+$/)) {
555
- parent.append('<div class="item barstyle'+style+'" data-value="'+value+'">'+text+'</div>');
565
+ parent.append('<div class="item barstyle'+style+' '+classes+'" data-value="'+value+'">'+text+'</div>');
556
566
 
557
567
  // loop style counter
558
568
  style++; style %= max;
@@ -568,37 +578,46 @@ function renderForm(form) {
568
578
 
569
579
  // only start counting and sizing bars if we actually have usable data
570
580
  if(data) {
581
+ // number of unique responses
582
+ var total = 0;
571
583
  // double loop so we can handle re-renderings of the form
572
584
  $(this).find('.item').each(function() {
573
585
  var name = $(this).attr('data-value');
574
586
 
575
587
  if(key in data) {
576
- var count = data[key][name];
577
- if(count) { sum += count; }
588
+ var count = data[key]['responses'][name];
589
+
590
+ total = data[key]['count'];
578
591
  }
579
592
  });
580
593
 
594
+ // insert the total into the counter label
595
+ $(this).find('span.count').each(function() {
596
+ $(this).text(total);
597
+ });
581
598
 
599
+ var oldTotal = $(this).attr('data-total');
582
600
  $(this).find('.item').each(function() {
583
601
  var name = $(this).attr('data-value');
584
602
  var oldCount = $(this).attr('data-count');
585
- var oldSum = $(this).attr('data-sum');
586
603
 
587
604
  if(key in data) {
588
- var count = data[key][name] || 0;
605
+ var count = data[key]['responses'][name] || 0;
589
606
  }
590
607
  else {
591
608
  var count = 0;
592
609
  }
593
610
 
594
- if(count != oldCount || sum != oldSum) {
595
- var percent = (sum) ? ((count/sum)*100)+'%' : '0%';
611
+ if(count != oldCount || total != oldTotal) {
612
+ var percent = (total) ? ((count/total)*100)+'%' : '0%';
596
613
 
597
614
  $(this).attr('data-count', count);
598
- $(this).attr('data-sum', sum);
599
615
  $(this).animate({width: percent});
600
616
  }
601
617
  });
618
+
619
+ // record the old total value so we only animate when it changes
620
+ $(this).attr('data-total', total);
602
621
  }
603
622
 
604
623
  $(this).addClass('rendered');
@@ -608,7 +627,8 @@ function renderForm(form) {
608
627
  }
609
628
 
610
629
  function connectControlChannel() {
611
- ws = new WebSocket('ws://' + location.host + '/control');
630
+ protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
631
+ ws = new WebSocket(protocol + location.host + '/control');
612
632
  ws.onopen = function() { connected(); };
613
633
  ws.onclose = function() { disconnected(); }
614
634
  ws.onmessage = function(m) { parseMessage(m.data); };
@@ -911,7 +931,7 @@ function toggleDebug () {
911
931
 
912
932
  function reloadSlides () {
913
933
  if (confirm('Are you sure you want to reload the slides?')) {
914
- loadSlides(loadSlidesBool, loadSlidesPrefix);
934
+ loadSlides(loadSlidesBool, loadSlidesPrefix, true);
915
935
  showSlide();
916
936
  }
917
937
  }
data/views/header.erb CHANGED
@@ -3,8 +3,6 @@
3
3
 
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/>
5
5
 
6
- <link rel="stylesheet" href="<%= @asset_path %>/css/reset.css" type="text/css"/>
7
-
8
6
  <% if @favicon %>
9
7
  <link rel="icon" href="<%= @favicon %>"/>
10
8
  <% end %>
@@ -7,7 +7,6 @@
7
7
  <link rel="icon" href="<%= @favicon %>"/>
8
8
  <% end %>
9
9
 
10
- <link rel="stylesheet" href="<%= @asset_path %>/css/reset.css" type="text/css"/>
11
10
  <link rel="stylesheet" href="<%= @asset_path %>/css/showoff.css" type="text/css"/>
12
11
 
13
12
  <link type="text/css" href="<%= @asset_path %>/css/fg.menu.css" media="screen" rel="stylesheet" />
data/views/onepage.erb CHANGED
@@ -11,11 +11,11 @@
11
11
 
12
12
  <% if @inline %>
13
13
 
14
- <%= inline_css(['reset.css', 'showoff.css', 'theme/ui.all.css', 'sh_style.css', 'onepage.css', "highlight/#{@highlightStyle}.css"], 'public/css') %>
14
+ <%= inline_css(['showoff.css', 'theme/ui.all.css', 'onepage.css', "highlight/#{@highlightStyle}.css"], 'public/css') %>
15
15
  <%= inline_css(css_files) %>
16
16
 
17
17
  <!--[if lte IE 8]>
18
- <%= inline_css('ie8.css') %>
18
+ <%= inline_css(['ie8.css'], 'public/css') %>
19
19
  <![endif]-->
20
20
 
21
21
  <%= inline_js(['jquery-2.1.4.min.js', 'jquery-print.js', 'showoff.js', 'onepage.js', 'highlight.pack.js'], 'public/js') %>
@@ -23,7 +23,7 @@
23
23
  <%= inline_js(js_files) %>
24
24
 
25
25
  <% else %>
26
- <% ['reset.css', 'showoff.css', 'theme/ui.all.css', 'onepage.css', "highlight/#{@highlightStyle}.css"].each do |css_file| %>
26
+ <% ['showoff.css', 'theme/ui.all.css', 'onepage.css', "highlight/#{@highlightStyle}.css"].each do |css_file| %>
27
27
  <link rel="stylesheet" href="<%= @asset_path %>/css/<%= css_file %>" type="text/css"/>
28
28
  <% end %>
29
29
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: showoff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-16 00:00:00.000000000 Z
11
+ date: 2015-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -268,7 +268,6 @@ files:
268
268
  - public/css/popdown.png
269
269
  - public/css/popout.png
270
270
  - public/css/presenter.css
271
- - public/css/reset.css
272
271
  - public/css/run_code-dim.png
273
272
  - public/css/run_code.png
274
273
  - public/css/showoff.css
data/public/css/reset.css DELETED
@@ -1,53 +0,0 @@
1
- /* http://meyerweb.com/eric/tools/css/reset/ */
2
- /* v1.0 | 20080212 */
3
-
4
- html, body, div, span, applet, object, iframe,
5
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6
- a, abbr, acronym, address, big, cite, code,
7
- del, dfn, em, font, img, ins, kbd, q, s, samp,
8
- small, strike, strong, sub, sup, tt, var,
9
- b, u, i, center,
10
- dl, dt, dd, ol, ul, li,
11
- fieldset, form, label, legend,
12
- table, caption, tbody, tfoot, thead, tr, th, td {
13
- margin: 0;
14
- padding: 0;
15
- border: 0;
16
- outline: 0;
17
- font-size: 100%;
18
- vertical-align: baseline;
19
- background: transparent;
20
- }
21
- body {
22
- line-height: 1;
23
- }
24
- ol, ul {
25
- list-style: none;
26
- }
27
- blockquote, q {
28
- quotes: none;
29
- }
30
- blockquote:before, blockquote:after,
31
- q:before, q:after {
32
- content: '';
33
- content: none;
34
- }
35
-
36
- /* remember to define focus styles! */
37
- :focus {
38
- outline: 0;
39
- }
40
-
41
- /* remember to highlight inserts somehow! */
42
- ins {
43
- text-decoration: none;
44
- }
45
- del {
46
- text-decoration: line-through;
47
- }
48
-
49
- /* tables still need 'cellspacing="0"' in the markup */
50
- table {
51
- border-collapse: collapse;
52
- border-spacing: 0;
53
- }