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 +4 -4
- data/bin/showoff +46 -13
- data/lib/showoff/version.rb +1 -1
- data/lib/showoff.rb +84 -45
- data/public/css/presenter.css +24 -0
- data/public/css/showoff.css +18 -1
- data/public/js/showoff.js +40 -20
- data/views/header.erb +0 -2
- data/views/header_mini.erb +0 -1
- data/views/onepage.erb +3 -3
- metadata +2 -3
- data/public/css/reset.css +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b168170069c1f28da7c3fe26bff21344cf8ad6bd
|
4
|
+
data.tar.gz: b42c67ff2736fbeda95819f6c3d61b4a1ea2980b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
-
|
149
|
-
|
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!
|
164
|
-
:port => options[:
|
165
|
-
:pres_file => options[:
|
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[:
|
170
|
-
:
|
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
|
|
data/lib/showoff/version.rb
CHANGED
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 '
|
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 =
|
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]
|
545
|
+
when /^\[\s+(\d*)\]$$/ # value = [ 5] (textarea)
|
540
546
|
str << form_element_textarea(id, code, $1)
|
541
|
-
when /^___+(?:\[(\d+)\])?$/ # value = ___[50]
|
547
|
+
when /^___+(?:\[(\d+)\])?$/ # value = ___[50] (text)
|
542
548
|
str << form_element_text(id, code, $1)
|
543
|
-
when /^\(
|
544
|
-
str << form_element_radio(id, code, rhs.scan(/\((
|
545
|
-
when /^\[
|
546
|
-
str << form_element_checkboxes(id, code, rhs.scan(/\[(
|
547
|
-
when /^\{(.*)\}$/ # value = {BOS, SFO, (NYC)}
|
548
|
-
str << form_element_select(id, code, rhs.scan(
|
549
|
-
when /^\{$/ # value = {
|
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 =
|
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 /^ +\((.+)
|
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 /\((
|
617
|
-
|
618
|
-
type
|
619
|
-
value
|
620
|
-
label
|
621
|
-
when /\[(
|
622
|
-
|
623
|
-
type
|
624
|
-
value
|
625
|
-
label
|
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,
|
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
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
1166
|
-
|
1200
|
+
responses[item] ||= 0
|
1201
|
+
responses[item] += 1
|
1167
1202
|
end
|
1168
1203
|
else
|
1169
|
-
|
1170
|
-
|
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
|
-
|
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
|
-
|
1337
|
+
views[slide] ||= Hash.new
|
1301
1338
|
# a bucket of slideviews for this address
|
1302
|
-
|
1339
|
+
views[slide][remote] ||= Array.new
|
1303
1340
|
# and add this slide viewing to the bucket
|
1304
|
-
|
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?
|
data/public/css/presenter.css
CHANGED
@@ -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;
|
data/public/css/showoff.css
CHANGED
@@ -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: #
|
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(
|
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('
|
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
|
-
|
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
|
533
|
-
var label
|
534
|
-
var
|
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
|
552
|
-
var 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
|
-
|
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 ||
|
595
|
-
var percent = (
|
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
|
-
|
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 %>
|
data/views/header_mini.erb
CHANGED
@@ -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(['
|
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
|
-
<% ['
|
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.
|
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-
|
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
|
-
}
|