showoff 0.10.2 → 0.11.0

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.
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
- }