showoff 0.16.1 → 0.16.2
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 +4 -4
 - data/bin/showoff +17 -8
 - data/lib/showoff.rb +250 -56
 - data/lib/showoff/version.rb +1 -1
 - data/public/css/presenter.css +7 -0
 - data/public/css/showoff.css +93 -1
 - data/public/js/presenter.js +21 -2
 - data/public/js/showoff.js +67 -15
 - data/views/header.erb +2 -0
 - data/views/header_mini.erb +2 -0
 - data/views/stats.erb +45 -54
 - metadata +16 -3
 - data/public/zoomline-0.0.1.html +0 -2085
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: bcb7404f722269ce7b4c571c0a7d34c72f76d684
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 7a2b4b825af14a88c58fccd9a851f51587894d9f
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 180720b95f90f0948642258fca4c84327da441b85d7b23bb00203272ee05c7ed07c85af8665a5938c5c1b8ca683e98887aa1b98514ae4b59d32c9e8dde41af2e
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 38036fa41718cf12953dd2db5a32cdc2b05840c11fea9652dfe8a525b0a095238a97bffbf643ddd5d4ea1ac57b0b2ce5af89929c7710761467e6323169dd3167
         
     | 
    
        data/bin/showoff
    CHANGED
    
    | 
         @@ -4,6 +4,7 @@ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require 'showoff'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'showoff/version'
         
     | 
| 
       6 
6 
     | 
    
         
             
            require 'rubygems'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'fidget'
         
     | 
| 
       7 
8 
     | 
    
         
             
            require 'gli'
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
            # See https://github.com/davetron5000/gli/issues/196 for rationale for this silly wrapper
         
     | 
| 
         @@ -98,24 +99,24 @@ module Wrapper 
     | 
|
| 
       98 
99 
     | 
    
         
             
              arg_name 'heroku_name'
         
     | 
| 
       99 
100 
     | 
    
         
             
              long_desc 'Creates the configuration files needed to Herokuize a Showoff presentation and then deploys it for you.'
         
     | 
| 
       100 
101 
     | 
    
         
             
              command :heroku do |c|
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
       102 
103 
     | 
    
         
             
                c.desc 'add password protection to your heroku site'
         
     | 
| 
       103 
104 
     | 
    
         
             
                c.flag [:p,:password]
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
       105 
106 
     | 
    
         
             
                c.desc 'force overwrite of existing Gemfile/.gems and config.ru files if they exist'
         
     | 
| 
       106 
107 
     | 
    
         
             
                c.switch [:f,:force]
         
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
       108 
109 
     | 
    
         
             
                c.action do |global_options,options,args|
         
     | 
| 
       109 
110 
     | 
    
         
             
                  raise "heroku_name is required" if args.empty?
         
     | 
| 
       110 
111 
     | 
    
         
             
                  raise "Name must start with a letter and can only contain lowercase letters, numbers, and dashes." unless args.first =~ /^[a-z][a-z1-9-]*$/
         
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
       112 
113 
     | 
    
         
             
                  unless system('git remote get-url heroku')
         
     | 
| 
       113 
114 
     | 
    
         
             
                    ShowOffUtils.command("heroku create #{args[0]}", "Please ensure that the heroku gem is installed and you're logged in.")
         
     | 
| 
       114 
115 
     | 
    
         
             
                  end
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
       116 
117 
     | 
    
         
             
                  if ShowOffUtils.heroku(args[0],options[:f],options[:p])
         
     | 
| 
       117 
118 
     | 
    
         
             
                    ShowOffUtils.command('bundle install', 'Please ensure that the bundler gem is installed.')
         
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
       119 
120 
     | 
    
         
             
                    begin
         
     | 
| 
       120 
121 
     | 
    
         
             
                      ShowOffUtils.command('git add Procfile Gemfile Gemfile.lock config.ru')
         
     | 
| 
       121 
122 
     | 
    
         
             
                      ShowOffUtils.command('git commit -m "Herokuized by Showoff"')
         
     | 
| 
         @@ -129,11 +130,11 @@ module Wrapper 
     | 
|
| 
       129 
130 
     | 
    
         
             
                      puts
         
     | 
| 
       130 
131 
     | 
    
         
             
                      puts 'When done, please run "git push heroku master"'
         
     | 
| 
       131 
132 
     | 
    
         
             
                    end
         
     | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
       133 
134 
     | 
    
         
             
                    if options[:p]
         
     | 
| 
       134 
135 
     | 
    
         
             
                      puts "CAREFUL: you are commiting your access password - anyone with read access to the repo can access the preso\n\n"
         
     | 
| 
       135 
136 
     | 
    
         
             
                    end
         
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
       137 
138 
     | 
    
         
             
                    puts 'Your presentation has been Herokuized. Run `heroku open` to see it.'
         
     | 
| 
       138 
139 
     | 
    
         
             
                  end
         
     | 
| 
       139 
140 
     | 
    
         
             
                end
         
     | 
| 
         @@ -159,6 +160,9 @@ module Wrapper 
     | 
|
| 
       159 
160 
     | 
    
         
             
                c.desc 'Disable content caching'
         
     | 
| 
       160 
161 
     | 
    
         
             
                c.switch :nocache
         
     | 
| 
       161 
162 
     | 
    
         | 
| 
      
 163 
     | 
    
         
            +
                c.desc 'Prevent the computer from sleeping during your presentation'
         
     | 
| 
      
 164 
     | 
    
         
            +
                c.switch :nosleep
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
       162 
166 
     | 
    
         
             
                c.desc 'Port on which to run'
         
     | 
| 
       163 
167 
     | 
    
         
             
                c.default_value "9090"
         
     | 
| 
       164 
168 
     | 
    
         
             
                c.flag [:p, :port]
         
     | 
| 
         @@ -230,6 +234,11 @@ module Wrapper 
     | 
|
| 
       230 
234 
     | 
    
         | 
| 
       231 
235 
     | 
    
         
             
              "
         
     | 
| 
       232 
236 
     | 
    
         | 
| 
      
 237 
     | 
    
         
            +
                  if options[:nosleep] and Fidget.current_process
         
     | 
| 
      
 238 
     | 
    
         
            +
                    puts '**** System sleep has been suspended. ****'
         
     | 
| 
      
 239 
     | 
    
         
            +
                    puts
         
     | 
| 
      
 240 
     | 
    
         
            +
                  end
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
       233 
242 
     | 
    
         
             
                  if options[:url]
         
     | 
| 
       234 
243 
     | 
    
         
             
                    ShowOffUtils.clone(options[:git_url], options[:git_branch], options[:git_path]) do
         
     | 
| 
       235 
244 
     | 
    
         
             
                      ShowOff.run!(options) do |server|
         
     | 
    
        data/lib/showoff.rb
    CHANGED
    
    | 
         @@ -140,6 +140,7 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       140 
140 
     | 
    
         
             
                @section_major = 0
         
     | 
| 
       141 
141 
     | 
    
         
             
                @section_minor = 0
         
     | 
| 
       142 
142 
     | 
    
         
             
                @section_title = settings.showoff_config['name'] rescue 'Showoff Presentation'
         
     | 
| 
      
 143 
     | 
    
         
            +
                @@slide_titles  = [] # a list of generated slide names, used for cross references later.
         
     | 
| 
       143 
144 
     | 
    
         | 
| 
       144 
145 
     | 
    
         
             
                @logger.debug settings.pres_template
         
     | 
| 
       145 
146 
     | 
    
         | 
| 
         @@ -161,12 +162,16 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       161 
162 
     | 
    
         
             
                begin
         
     | 
| 
       162 
163 
     | 
    
         
             
                  @@counter = JSON.parse(File.read("#{settings.statsdir}/#{settings.viewstats}"))
         
     | 
| 
       163 
164 
     | 
    
         | 
| 
       164 
     | 
    
         
            -
                  # port old format stats
         
     | 
| 
      
 165 
     | 
    
         
            +
                  # TODO: remove this logic 4/15/2017: port old format stats
         
     | 
| 
       165 
166 
     | 
    
         
             
                  unless @@counter.has_key? 'user_agents'
         
     | 
| 
       166 
     | 
    
         
            -
                    @@counter 
     | 
| 
      
 167 
     | 
    
         
            +
                    @@counter['pageviews'] = @@counter
         
     | 
| 
       167 
168 
     | 
    
         
             
                  end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                  @@counter['current']     ||= {}
         
     | 
| 
      
 171 
     | 
    
         
            +
                  @@counter['pageviews']   ||= {}
         
     | 
| 
      
 172 
     | 
    
         
            +
                  @@counter['user_agents'] ||= {}
         
     | 
| 
       168 
173 
     | 
    
         
             
                rescue
         
     | 
| 
       169 
     | 
    
         
            -
                  @@counter = { 'user_agents' => {}, 'pageviews' => {} }
         
     | 
| 
      
 174 
     | 
    
         
            +
                  @@counter = { 'user_agents' => {}, 'pageviews' => {}, 'current' => {} }
         
     | 
| 
       170 
175 
     | 
    
         
             
                end
         
     | 
| 
       171 
176 
     | 
    
         | 
| 
       172 
177 
     | 
    
         
             
                # keeps track of form responses. In memory to avoid concurrence issues.
         
     | 
| 
         @@ -395,11 +400,9 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       395 
400 
     | 
    
         | 
| 
       396 
401 
     | 
    
         
             
                    # name the slide. If we've got multiple slides in this file, we'll have a sequence number
         
     | 
| 
       397 
402 
     | 
    
         
             
                    # include that sequence number to index directly into that content
         
     | 
| 
       398 
     | 
    
         
            -
                     
     | 
| 
       399 
     | 
    
         
            -
             
     | 
| 
       400 
     | 
    
         
            -
                     
     | 
| 
       401 
     | 
    
         
            -
                      content += "<div class=\"content #{classes}\" ref=\"#{name}\">\n"
         
     | 
| 
       402 
     | 
    
         
            -
                    end
         
     | 
| 
      
 403 
     | 
    
         
            +
                    ref = seq ? "#{name}:#{seq.to_s}" : name
         
     | 
| 
      
 404 
     | 
    
         
            +
                    content += "<div class=\"content #{classes}\" ref=\"#{ref}\">\n"
         
     | 
| 
      
 405 
     | 
    
         
            +
                    @@slide_titles << ref
         
     | 
| 
       403 
406 
     | 
    
         | 
| 
       404 
407 
     | 
    
         
             
                    # renderers like wkhtmltopdf needs an <h1> tag to use for a section title, but only when printing.
         
     | 
| 
       405 
408 
     | 
    
         
             
                    if opts[:print]
         
     | 
| 
         @@ -492,12 +495,6 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       492 
495 
     | 
    
         
             
                  # Turn this into a document for munging
         
     | 
| 
       493 
496 
     | 
    
         
             
                  doc = Nokogiri::HTML::DocumentFragment.parse(result)
         
     | 
| 
       494 
497 
     | 
    
         | 
| 
       495 
     | 
    
         
            -
                  if opts[:section]
         
     | 
| 
       496 
     | 
    
         
            -
                    doc.css('div.notes-section').each do |section|
         
     | 
| 
       497 
     | 
    
         
            -
                      section.remove unless section.attr('class').split.include? opts[:section]
         
     | 
| 
       498 
     | 
    
         
            -
                    end
         
     | 
| 
       499 
     | 
    
         
            -
                  end
         
     | 
| 
       500 
     | 
    
         
            -
             
     | 
| 
       501 
498 
     | 
    
         
             
                  filename = File.join(settings.pres_dir, '_notes', "#{name}.md")
         
     | 
| 
       502 
499 
     | 
    
         
             
                  @logger.debug "personal notes filename: #{filename}"
         
     | 
| 
       503 
500 
     | 
    
         
             
                  if [nil, 'notes'].include? opts[:section] and File.file? filename
         
     | 
| 
         @@ -519,9 +516,68 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       519 
516 
     | 
    
         
             
                    end
         
     | 
| 
       520 
517 
     | 
    
         
             
                  end
         
     | 
| 
       521 
518 
     | 
    
         | 
| 
       522 
     | 
    
         
            -
                   
     | 
| 
      
 519 
     | 
    
         
            +
                  doc.css('.callout.glossary').each do |item|
         
     | 
| 
      
 520 
     | 
    
         
            +
                    next unless item.content =~ /^([^|]+)\|([^:]+):(.*)$/
         
     | 
| 
      
 521 
     | 
    
         
            +
                    item['data-term']   = $1
         
     | 
| 
      
 522 
     | 
    
         
            +
                    item['data-target'] = $2
         
     | 
| 
      
 523 
     | 
    
         
            +
                    item['data-text']   = $3
         
     | 
| 
      
 524 
     | 
    
         
            +
                    item.content        = $3
         
     | 
| 
      
 525 
     | 
    
         
            +
             
     | 
| 
      
 526 
     | 
    
         
            +
                    glossary = (item.attr('class').split - ['callout', 'glossary']).first
         
     | 
| 
      
 527 
     | 
    
         
            +
                    address  = glossary ? "#{glossary}/#{$2}" : $2
         
     | 
| 
      
 528 
     | 
    
         
            +
                    frag     = "<a class=\"processed label\" href=\"glossary://#{address}\">#{$1}</a>"
         
     | 
| 
      
 529 
     | 
    
         
            +
             
     | 
| 
      
 530 
     | 
    
         
            +
                    item.children.before(Nokogiri::HTML::DocumentFragment.parse(frag))
         
     | 
| 
      
 531 
     | 
    
         
            +
                  end
         
     | 
| 
      
 532 
     | 
    
         
            +
             
     | 
| 
      
 533 
     | 
    
         
            +
                  # Process links
         
     | 
| 
       523 
534 
     | 
    
         
             
                  doc.css('a').each do |link|
         
     | 
| 
       524 
     | 
    
         
            -
                     
     | 
| 
      
 535 
     | 
    
         
            +
                    next if link['href'].start_with? '#'
         
     | 
| 
      
 536 
     | 
    
         
            +
                    next if link['class'].split.include? 'processed' rescue nil
         
     | 
| 
      
 537 
     | 
    
         
            +
             
     | 
| 
      
 538 
     | 
    
         
            +
                    # If these are glossary links, populate the notes/handouts sections
         
     | 
| 
      
 539 
     | 
    
         
            +
                    if link['href'].start_with? 'glossary://'
         
     | 
| 
      
 540 
     | 
    
         
            +
                      doc.add_child '<div class="notes-section notes"></div>' if doc.css('div.notes-section.notes').empty?
         
     | 
| 
      
 541 
     | 
    
         
            +
                      doc.add_child '<div class="notes-section handouts"></div>' if doc.css('div.notes-section.handouts').empty?
         
     | 
| 
      
 542 
     | 
    
         
            +
             
     | 
| 
      
 543 
     | 
    
         
            +
                      term = link.content
         
     | 
| 
      
 544 
     | 
    
         
            +
                      text = link['title']
         
     | 
| 
      
 545 
     | 
    
         
            +
                      href = link['href']
         
     | 
| 
      
 546 
     | 
    
         
            +
                      href.slice!('glossary://')
         
     | 
| 
      
 547 
     | 
    
         
            +
             
     | 
| 
      
 548 
     | 
    
         
            +
                      parts  = href.split('/')
         
     | 
| 
      
 549 
     | 
    
         
            +
                      target = parts.pop
         
     | 
| 
      
 550 
     | 
    
         
            +
                      name   = parts.pop # either the glossary name or nil
         
     | 
| 
      
 551 
     | 
    
         
            +
             
     | 
| 
      
 552 
     | 
    
         
            +
                      link['class']  = 'term'
         
     | 
| 
      
 553 
     | 
    
         
            +
             
     | 
| 
      
 554 
     | 
    
         
            +
                      label = link.clone
         
     | 
| 
      
 555 
     | 
    
         
            +
                      label['class'] = 'label processed'
         
     | 
| 
      
 556 
     | 
    
         
            +
             
     | 
| 
      
 557 
     | 
    
         
            +
                      frag = Nokogiri::HTML::DocumentFragment.parse('<p></p>')
         
     | 
| 
      
 558 
     | 
    
         
            +
                      definition = frag.children.first
         
     | 
| 
      
 559 
     | 
    
         
            +
                      definition['class'] = "callout glossary #{name}"
         
     | 
| 
      
 560 
     | 
    
         
            +
                      definition['data-term']   = term
         
     | 
| 
      
 561 
     | 
    
         
            +
                      definition['data-target'] = target
         
     | 
| 
      
 562 
     | 
    
         
            +
                      definition['data-text']   = text
         
     | 
| 
      
 563 
     | 
    
         
            +
                      definition.content = text
         
     | 
| 
      
 564 
     | 
    
         
            +
                      definition.children.before(label)
         
     | 
| 
      
 565 
     | 
    
         
            +
             
     | 
| 
      
 566 
     | 
    
         
            +
                      [doc.css('div.notes-section.notes'), doc.css('div.notes-section.handouts')].each do |section|
         
     | 
| 
      
 567 
     | 
    
         
            +
                        section.first.add_child(definition.clone)
         
     | 
| 
      
 568 
     | 
    
         
            +
                      end
         
     | 
| 
      
 569 
     | 
    
         
            +
             
     | 
| 
      
 570 
     | 
    
         
            +
                    else
         
     | 
| 
      
 571 
     | 
    
         
            +
                      # Add a target so we open all external links from notes in a new window
         
     | 
| 
      
 572 
     | 
    
         
            +
                      link.set_attribute('target', '_blank')
         
     | 
| 
      
 573 
     | 
    
         
            +
                    end
         
     | 
| 
      
 574 
     | 
    
         
            +
                  end
         
     | 
| 
      
 575 
     | 
    
         
            +
             
     | 
| 
      
 576 
     | 
    
         
            +
                  # finally, remove any sections we don't want to print
         
     | 
| 
      
 577 
     | 
    
         
            +
                  if opts[:section]
         
     | 
| 
      
 578 
     | 
    
         
            +
                    doc.css('div.notes-section').each do |section|
         
     | 
| 
      
 579 
     | 
    
         
            +
                      section.remove unless section.attr('class').split.include? opts[:section]
         
     | 
| 
      
 580 
     | 
    
         
            +
                    end
         
     | 
| 
       525 
581 
     | 
    
         
             
                  end
         
     | 
| 
       526 
582 
     | 
    
         | 
| 
       527 
583 
     | 
    
         
             
                  doc.to_html
         
     | 
| 
         @@ -552,31 +608,87 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       552 
608 
     | 
    
         
             
                end
         
     | 
| 
       553 
609 
     | 
    
         | 
| 
       554 
610 
     | 
    
         
             
                def process_content_for_all_slides(content, num_slides, opts={})
         
     | 
| 
      
 611 
     | 
    
         
            +
                  # this has to be text replacement for now, since the string can appear in any context
         
     | 
| 
       555 
612 
     | 
    
         
             
                  content.gsub!("~~~NUM_SLIDES~~~", num_slides.to_s)
         
     | 
| 
      
 613 
     | 
    
         
            +
                  doc = Nokogiri::HTML::DocumentFragment.parse(content)
         
     | 
| 
       556 
614 
     | 
    
         | 
| 
       557 
615 
     | 
    
         
             
                  # Should we build a table of contents?
         
     | 
| 
       558 
616 
     | 
    
         
             
                  if opts[:toc]
         
     | 
| 
       559 
     | 
    
         
            -
                     
     | 
| 
       560 
     | 
    
         
            -
             
     | 
| 
       561 
     | 
    
         
            -
                     
     | 
| 
       562 
     | 
    
         
            -
             
     | 
| 
       563 
     | 
    
         
            -
             
     | 
| 
       564 
     | 
    
         
            -
             
     | 
| 
       565 
     | 
    
         
            -
             
     | 
| 
       566 
     | 
    
         
            -
                       
     | 
| 
       567 
     | 
    
         
            -
                      toc.add_child(entry)
         
     | 
| 
       568 
     | 
    
         
            -
             
     | 
| 
       569 
     | 
    
         
            -
                      link = Nokogiri::XML::Node.new('a', frag)
         
     | 
| 
       570 
     | 
    
         
            -
                      link['href'] = "##{section.parent.parent['id']}"
         
     | 
| 
       571 
     | 
    
         
            -
                      link.content = section.content
         
     | 
| 
       572 
     | 
    
         
            -
                      entry.add_child(link)
         
     | 
| 
      
 617 
     | 
    
         
            +
                    toc = Nokogiri::HTML::DocumentFragment.parse("<p id=\"toc\"></p>")
         
     | 
| 
      
 618 
     | 
    
         
            +
             
     | 
| 
      
 619 
     | 
    
         
            +
                    doc.css('div.subsection > h1:not(.section_title)').each do |section|
         
     | 
| 
      
 620 
     | 
    
         
            +
                      href = section.parent.parent['id']
         
     | 
| 
      
 621 
     | 
    
         
            +
                      frag = "<div class=\"tocentry\"><a href=\"##{href}\">#{section.content}</a></div>"
         
     | 
| 
      
 622 
     | 
    
         
            +
                      link = Nokogiri::HTML::DocumentFragment.parse(frag)
         
     | 
| 
      
 623 
     | 
    
         
            +
             
     | 
| 
      
 624 
     | 
    
         
            +
                      toc.children.first.add_child(link)
         
     | 
| 
       573 
625 
     | 
    
         
             
                    end
         
     | 
| 
       574 
626 
     | 
    
         | 
| 
       575 
627 
     | 
    
         
             
                    # swap out the tag, if found, with the table of contents
         
     | 
| 
       576 
     | 
    
         
            -
                     
     | 
| 
      
 628 
     | 
    
         
            +
                    doc.at('p:contains("~~~TOC~~~")').replace(toc)
         
     | 
| 
       577 
629 
     | 
    
         
             
                  end
         
     | 
| 
       578 
630 
     | 
    
         | 
| 
       579 
     | 
    
         
            -
                  content
         
     | 
| 
      
 631 
     | 
    
         
            +
                  doc.css('.slide.glossary .content').each do |glossary|
         
     | 
| 
      
 632 
     | 
    
         
            +
                    name = (glossary.attr('class').split - ['content', 'glossary']).first
         
     | 
| 
      
 633 
     | 
    
         
            +
                    list = Nokogiri::HTML::DocumentFragment.parse('<ul class="glossary terms"></ul>')
         
     | 
| 
      
 634 
     | 
    
         
            +
                    seen = []
         
     | 
| 
      
 635 
     | 
    
         
            +
             
     | 
| 
      
 636 
     | 
    
         
            +
                    doc.css('.callout.glossary').each do |item|
         
     | 
| 
      
 637 
     | 
    
         
            +
                      target = (item.attr('class').split - ['callout', 'glossary']).first
         
     | 
| 
      
 638 
     | 
    
         
            +
             
     | 
| 
      
 639 
     | 
    
         
            +
                      # if the name matches or if we didn't name it to begin with.
         
     | 
| 
      
 640 
     | 
    
         
            +
                      next unless target == name
         
     | 
| 
      
 641 
     | 
    
         
            +
             
     | 
| 
      
 642 
     | 
    
         
            +
                      # the definition can exist in multiple places, so de-dup it here
         
     | 
| 
      
 643 
     | 
    
         
            +
                      term = item.attr('data-term')
         
     | 
| 
      
 644 
     | 
    
         
            +
                      next if seen.include? term
         
     | 
| 
      
 645 
     | 
    
         
            +
                      seen << term
         
     | 
| 
      
 646 
     | 
    
         
            +
             
     | 
| 
      
 647 
     | 
    
         
            +
                      # excrutiatingly find the parent slide content and grab the ref
         
     | 
| 
      
 648 
     | 
    
         
            +
                      # in a library less shitty, this would be something like
         
     | 
| 
      
 649 
     | 
    
         
            +
                      # $(this).parent().siblings('.content').attr('ref')
         
     | 
| 
      
 650 
     | 
    
         
            +
                      href = nil
         
     | 
| 
      
 651 
     | 
    
         
            +
                      item.ancestors('.slide').first.traverse do |element|
         
     | 
| 
      
 652 
     | 
    
         
            +
                        next if element['class'].nil?
         
     | 
| 
      
 653 
     | 
    
         
            +
                        next unless element['class'].split.include? 'content'
         
     | 
| 
      
 654 
     | 
    
         
            +
             
     | 
| 
      
 655 
     | 
    
         
            +
                        href = element.attr('ref').gsub('/', '_')
         
     | 
| 
      
 656 
     | 
    
         
            +
                      end
         
     | 
| 
      
 657 
     | 
    
         
            +
             
     | 
| 
      
 658 
     | 
    
         
            +
                      text   = item.attr('data-text')
         
     | 
| 
      
 659 
     | 
    
         
            +
                      link   = item.attr('data-target')
         
     | 
| 
      
 660 
     | 
    
         
            +
                      page   = glossary.attr('ref')
         
     | 
| 
      
 661 
     | 
    
         
            +
                      anchor = "#{page}+#{link}"
         
     | 
| 
      
 662 
     | 
    
         
            +
                      next if href.nil? or text.nil? or link.nil?
         
     | 
| 
      
 663 
     | 
    
         
            +
             
     | 
| 
      
 664 
     | 
    
         
            +
                      frag = "<li><a id=\"#{anchor}\" class=\"label\">#{term}</a>#{text}<a href=\"##{href}\" class=\"return\">↩</a></li>"
         
     | 
| 
      
 665 
     | 
    
         
            +
                      item = Nokogiri::HTML::DocumentFragment.parse(frag)
         
     | 
| 
      
 666 
     | 
    
         
            +
             
     | 
| 
      
 667 
     | 
    
         
            +
                      list.children.first.add_child(item)
         
     | 
| 
      
 668 
     | 
    
         
            +
                    end
         
     | 
| 
      
 669 
     | 
    
         
            +
             
     | 
| 
      
 670 
     | 
    
         
            +
                    glossary.add_child(list)
         
     | 
| 
      
 671 
     | 
    
         
            +
                  end
         
     | 
| 
      
 672 
     | 
    
         
            +
             
     | 
| 
      
 673 
     | 
    
         
            +
                  # now fix all the links to point to the glossary page
         
     | 
| 
      
 674 
     | 
    
         
            +
                  doc.css('a').each do |link|
         
     | 
| 
      
 675 
     | 
    
         
            +
                    next if link['href'].nil?
         
     | 
| 
      
 676 
     | 
    
         
            +
                    next unless link['href'].start_with? 'glossary://'
         
     | 
| 
      
 677 
     | 
    
         
            +
             
     | 
| 
      
 678 
     | 
    
         
            +
                    href = link['href']
         
     | 
| 
      
 679 
     | 
    
         
            +
                    href.slice!('glossary://')
         
     | 
| 
      
 680 
     | 
    
         
            +
             
     | 
| 
      
 681 
     | 
    
         
            +
                    parts  = href.split('/')
         
     | 
| 
      
 682 
     | 
    
         
            +
                    target = parts.pop
         
     | 
| 
      
 683 
     | 
    
         
            +
                    name   = parts.pop # either the glossary name or nil
         
     | 
| 
      
 684 
     | 
    
         
            +
             
     | 
| 
      
 685 
     | 
    
         
            +
                    classes = name.nil? ? ".slide.glossary" : ".slide.glossary.#{name}"
         
     | 
| 
      
 686 
     | 
    
         
            +
                    href    = doc.at("#{classes} .content").attr('ref') rescue nil
         
     | 
| 
      
 687 
     | 
    
         
            +
             
     | 
| 
      
 688 
     | 
    
         
            +
                    link['href'] = "##{href}+#{target}"
         
     | 
| 
      
 689 
     | 
    
         
            +
                  end
         
     | 
| 
      
 690 
     | 
    
         
            +
             
     | 
| 
      
 691 
     | 
    
         
            +
                  doc.to_html
         
     | 
| 
       580 
692 
     | 
    
         
             
                end
         
     | 
| 
       581 
693 
     | 
    
         | 
| 
       582 
694 
     | 
    
         
             
                # Find any lines that start with a <p>.(something), remove the ones tagged with
         
     | 
| 
         @@ -991,6 +1103,14 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       991 
1103 
     | 
    
         
             
                  # Provide a button in the sidebar for interactive editing if configured
         
     | 
| 
       992 
1104 
     | 
    
         
             
                  @edit     = settings.showoff_config['edit'] if @review
         
     | 
| 
       993 
1105 
     | 
    
         | 
| 
      
 1106 
     | 
    
         
            +
                  # store a cookie to tell clients apart. More reliable than using IP due to proxies, etc.
         
     | 
| 
      
 1107 
     | 
    
         
            +
                  unless request.cookies['client_id']
         
     | 
| 
      
 1108 
     | 
    
         
            +
                    @client_id = guid()
         
     | 
| 
      
 1109 
     | 
    
         
            +
                    response.set_cookie('client_id', @client_id)
         
     | 
| 
      
 1110 
     | 
    
         
            +
                  else
         
     | 
| 
      
 1111 
     | 
    
         
            +
                    @client_id = request.cookies['client_id']
         
     | 
| 
      
 1112 
     | 
    
         
            +
                  end
         
     | 
| 
      
 1113 
     | 
    
         
            +
             
     | 
| 
       994 
1114 
     | 
    
         
             
                  erb :index
         
     | 
| 
       995 
1115 
     | 
    
         
             
                end
         
     | 
| 
       996 
1116 
     | 
    
         | 
| 
         @@ -1046,6 +1166,7 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1046 
1166 
     | 
    
         | 
| 
       1047 
1167 
     | 
    
         
             
                  # if we have a cache and we're not asking to invalidate it
         
     | 
| 
       1048 
1168 
     | 
    
         
             
                  return @@cache if (@@cache and params['cache'] != 'clear')
         
     | 
| 
      
 1169 
     | 
    
         
            +
                  @@slide_titles = []
         
     | 
| 
       1049 
1170 
     | 
    
         
             
                  content = get_slides_html(:static=>static)
         
     | 
| 
       1050 
1171 
     | 
    
         | 
| 
       1051 
1172 
     | 
    
         
             
                  # allow command line cache disabling
         
     | 
| 
         @@ -1081,12 +1202,79 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1081 
1202 
     | 
    
         
             
                  erb :download
         
     | 
| 
       1082 
1203 
     | 
    
         
             
                end
         
     | 
| 
       1083 
1204 
     | 
    
         | 
| 
      
 1205 
     | 
    
         
            +
                def stats_data()
         
     | 
| 
      
 1206 
     | 
    
         
            +
                  data = {}
         
     | 
| 
      
 1207 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 1208 
     | 
    
         
            +
             
     | 
| 
      
 1209 
     | 
    
         
            +
                    # what are viewers looking at right now?
         
     | 
| 
      
 1210 
     | 
    
         
            +
                    now = Time.now.to_i # let's throw away viewers who haven't done anything in 5m
         
     | 
| 
      
 1211 
     | 
    
         
            +
                    active  = @@counter['current'].select {|client, view| (now - view[1]).abs < 300 }
         
     | 
| 
      
 1212 
     | 
    
         
            +
             
     | 
| 
      
 1213 
     | 
    
         
            +
                    # percentage of stray viewers
         
     | 
| 
      
 1214 
     | 
    
         
            +
                    stray   = active.select {|client, view| view[0] != @@current[:name] }
         
     | 
| 
      
 1215 
     | 
    
         
            +
                    stray_p = ((stray.size.to_f / active.size.to_f) * 100).to_i rescue 0
         
     | 
| 
      
 1216 
     | 
    
         
            +
                    data['stray_p'] = stray_p
         
     | 
| 
      
 1217 
     | 
    
         
            +
             
     | 
| 
      
 1218 
     | 
    
         
            +
                    # percentage of idle viewers
         
     | 
| 
      
 1219 
     | 
    
         
            +
                    idle    = @@counter['current'].size - active.size
         
     | 
| 
      
 1220 
     | 
    
         
            +
                    idle_p  = ((idle.to_f / @@counter['current'].size.to_f) * 100).to_i rescue 0
         
     | 
| 
      
 1221 
     | 
    
         
            +
                    data['idle_p']  = idle_p
         
     | 
| 
      
 1222 
     | 
    
         
            +
             
     | 
| 
      
 1223 
     | 
    
         
            +
                    viewers = @@slide_titles.map do |slide|
         
     | 
| 
      
 1224 
     | 
    
         
            +
                      count = active.select {|client, view| view[0] == slide }.size
         
     | 
| 
      
 1225 
     | 
    
         
            +
                      flags = (slide == @@current[:name]) ? 'current' : nil
         
     | 
| 
      
 1226 
     | 
    
         
            +
                      [count, slide, nil, flags]
         
     | 
| 
      
 1227 
     | 
    
         
            +
                    end
         
     | 
| 
      
 1228 
     | 
    
         
            +
             
     | 
| 
      
 1229 
     | 
    
         
            +
                    # trim the ends, if nobody's looking we don't much care.
         
     | 
| 
      
 1230 
     | 
    
         
            +
                    viewers.pop while viewers.last[0] == 0
         
     | 
| 
      
 1231 
     | 
    
         
            +
                    viewers.shift while viewers.first[0] == 0
         
     | 
| 
      
 1232 
     | 
    
         
            +
                    viewmax = viewers.max_by {|view| view[0] }.first
         
     | 
| 
      
 1233 
     | 
    
         
            +
             
     | 
| 
      
 1234 
     | 
    
         
            +
                    data['viewers'] = viewers
         
     | 
| 
      
 1235 
     | 
    
         
            +
                    data['viewmax'] = viewmax
         
     | 
| 
      
 1236 
     | 
    
         
            +
                  rescue => e
         
     | 
| 
      
 1237 
     | 
    
         
            +
                    @logger.warn "Not enough data to generate pageviews."
         
     | 
| 
      
 1238 
     | 
    
         
            +
                    @logger.debug e.message
         
     | 
| 
      
 1239 
     | 
    
         
            +
                    @logger.debug e.backtrace.first
         
     | 
| 
      
 1240 
     | 
    
         
            +
                  end
         
     | 
| 
      
 1241 
     | 
    
         
            +
             
     | 
| 
      
 1242 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 1243 
     | 
    
         
            +
                    # current elapsed time for the zoomline view
         
     | 
| 
      
 1244 
     | 
    
         
            +
                    elapsed = @@slide_titles.map do |slide|
         
     | 
| 
      
 1245 
     | 
    
         
            +
                      if @@counter['pageviews'][slide].nil?
         
     | 
| 
      
 1246 
     | 
    
         
            +
                        time = 0
         
     | 
| 
      
 1247 
     | 
    
         
            +
                      else
         
     | 
| 
      
 1248 
     | 
    
         
            +
                        time = @@counter['pageviews'][slide].inject(0) do |outer, (viewer, views)|
         
     | 
| 
      
 1249 
     | 
    
         
            +
                          outer += views.inject(0) { |inner, view| inner += view['elapsed'] }
         
     | 
| 
      
 1250 
     | 
    
         
            +
                        end
         
     | 
| 
      
 1251 
     | 
    
         
            +
                      end
         
     | 
| 
      
 1252 
     | 
    
         
            +
                      string = Time.at(time).gmtime.strftime('%M:%S')
         
     | 
| 
      
 1253 
     | 
    
         
            +
                      flags  = (slide == @@current[:name]) ? 'current' : nil
         
     | 
| 
      
 1254 
     | 
    
         
            +
             
     | 
| 
      
 1255 
     | 
    
         
            +
                      [ time, slide, string, flags ]
         
     | 
| 
      
 1256 
     | 
    
         
            +
                    end
         
     | 
| 
      
 1257 
     | 
    
         
            +
                    maxtime = elapsed.max_by {|view| view[0] }.first
         
     | 
| 
      
 1258 
     | 
    
         
            +
             
     | 
| 
      
 1259 
     | 
    
         
            +
                    data['elapsed'] = elapsed
         
     | 
| 
      
 1260 
     | 
    
         
            +
                    data['maxtime'] = maxtime
         
     | 
| 
      
 1261 
     | 
    
         
            +
                  rescue => e
         
     | 
| 
      
 1262 
     | 
    
         
            +
                    # expected if this is loaded before a presentation has been compiled
         
     | 
| 
      
 1263 
     | 
    
         
            +
                    @logger.warn "Not enough data to generate elapsed time."
         
     | 
| 
      
 1264 
     | 
    
         
            +
                    @logger.debug e.message
         
     | 
| 
      
 1265 
     | 
    
         
            +
                    @logger.debug e.backtrace.first
         
     | 
| 
      
 1266 
     | 
    
         
            +
                  end
         
     | 
| 
      
 1267 
     | 
    
         
            +
             
     | 
| 
      
 1268 
     | 
    
         
            +
                  data.to_json
         
     | 
| 
      
 1269 
     | 
    
         
            +
                end
         
     | 
| 
      
 1270 
     | 
    
         
            +
             
     | 
| 
       1084 
1271 
     | 
    
         
             
                def stats()
         
     | 
| 
       1085 
     | 
    
         
            -
                  if  
     | 
| 
      
 1272 
     | 
    
         
            +
                  if localhost?
         
     | 
| 
       1086 
1273 
     | 
    
         
             
                    # the presenter should have full stats in the erb
         
     | 
| 
       1087 
1274 
     | 
    
         
             
                    @counter = @@counter['pageviews']
         
     | 
| 
       1088 
1275 
     | 
    
         
             
                  end
         
     | 
| 
       1089 
1276 
     | 
    
         | 
| 
      
 1277 
     | 
    
         
            +
                  # for the full page view. Maybe to be disappeared
         
     | 
| 
       1090 
1278 
     | 
    
         
             
                  @all = Hash.new
         
     | 
| 
       1091 
1279 
     | 
    
         
             
                  @@counter['pageviews'].each do |slide, stats|
         
     | 
| 
       1092 
1280 
     | 
    
         
             
                    @all[slide] = 0
         
     | 
| 
         @@ -1095,10 +1283,6 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1095 
1283 
     | 
    
         
             
                    end
         
     | 
| 
       1096 
1284 
     | 
    
         
             
                  end
         
     | 
| 
       1097 
1285 
     | 
    
         | 
| 
       1098 
     | 
    
         
            -
                  # most and least five viewed slides
         
     | 
| 
       1099 
     | 
    
         
            -
                  @least = @all.sort_by {|slide, time| time}[0..4]
         
     | 
| 
       1100 
     | 
    
         
            -
                  @most = @all.sort_by {|slide, time| -time}[0..4]
         
     | 
| 
       1101 
     | 
    
         
            -
             
     | 
| 
       1102 
1286 
     | 
    
         
             
                  erb :stats
         
     | 
| 
       1103 
1287 
     | 
    
         
             
                end
         
     | 
| 
       1104 
1288 
     | 
    
         | 
| 
         @@ -1131,7 +1315,7 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1131 
1315 
     | 
    
         
             
              end
         
     | 
| 
       1132 
1316 
     | 
    
         | 
| 
       1133 
1317 
     | 
    
         | 
| 
       1134 
     | 
    
         
            -
             
     | 
| 
      
 1318 
     | 
    
         
            +
              def self.do_static(args, opts = {})
         
     | 
| 
       1135 
1319 
     | 
    
         
             
                  args ||= [] # handle nil arguments
         
     | 
| 
       1136 
1320 
     | 
    
         
             
                  what   = args[0] || "index"
         
     | 
| 
       1137 
1321 
     | 
    
         
             
                  opt    = args[1]
         
     | 
| 
         @@ -1319,18 +1503,20 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1319 
1503 
     | 
    
         
             
                (0..15).to_a.map{|a| rand(16).to_s(16)}.join
         
     | 
| 
       1320 
1504 
     | 
    
         
             
              end
         
     | 
| 
       1321 
1505 
     | 
    
         | 
| 
       1322 
     | 
    
         
            -
              def  
     | 
| 
      
 1506 
     | 
    
         
            +
              def valid_presenter_cookie?
         
     | 
| 
      
 1507 
     | 
    
         
            +
                return false if @@cookie.nil?
         
     | 
| 
       1323 
1508 
     | 
    
         
             
                (request.cookies['presenter'] == @@cookie)
         
     | 
| 
       1324 
1509 
     | 
    
         
             
              end
         
     | 
| 
       1325 
1510 
     | 
    
         | 
| 
       1326 
1511 
     | 
    
         
             
              post '/form/:id' do |id|
         
     | 
| 
       1327 
     | 
    
         
            -
                 
     | 
| 
      
 1512 
     | 
    
         
            +
                client_id = request.cookies['client_id']
         
     | 
| 
      
 1513 
     | 
    
         
            +
                @logger.warn("Saving form answers from ip:#{request.ip} with ID of #{client_id} for id:##{id}")
         
     | 
| 
       1328 
1514 
     | 
    
         | 
| 
       1329 
1515 
     | 
    
         
             
                form = params.reject { |k,v| ['splat', 'captures', 'id'].include? k }
         
     | 
| 
       1330 
1516 
     | 
    
         | 
| 
       1331 
1517 
     | 
    
         
             
                # make sure we've got a bucket for this form, then save our answers
         
     | 
| 
       1332 
1518 
     | 
    
         
             
                @@forms[id] ||= {}
         
     | 
| 
       1333 
     | 
    
         
            -
                @@forms[id][ 
     | 
| 
      
 1519 
     | 
    
         
            +
                @@forms[id][client_id] = form
         
     | 
| 
       1334 
1520 
     | 
    
         | 
| 
       1335 
1521 
     | 
    
         
             
                form.to_json
         
     | 
| 
       1336 
1522 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1445,13 +1631,13 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1445 
1631 
     | 
    
         
             
                      begin
         
     | 
| 
       1446 
1632 
     | 
    
         
             
                        control = JSON.parse(data)
         
     | 
| 
       1447 
1633 
     | 
    
         | 
| 
       1448 
     | 
    
         
            -
                        @logger. 
     | 
| 
      
 1634 
     | 
    
         
            +
                        @logger.debug "#{control.inspect}"
         
     | 
| 
       1449 
1635 
     | 
    
         | 
| 
       1450 
1636 
     | 
    
         
             
                        case control['message']
         
     | 
| 
       1451 
1637 
     | 
    
         
             
                        when 'update'
         
     | 
| 
       1452 
1638 
     | 
    
         
             
                          # websockets don't use the same auth standards
         
     | 
| 
       1453 
1639 
     | 
    
         
             
                          # we use a session cookie to identify the presenter
         
     | 
| 
       1454 
     | 
    
         
            -
                          if  
     | 
| 
      
 1640 
     | 
    
         
            +
                          if valid_presenter_cookie?
         
     | 
| 
       1455 
1641 
     | 
    
         
             
                            name  = control['name']
         
     | 
| 
       1456 
1642 
     | 
    
         
             
                            slide = control['slide'].to_i
         
     | 
| 
       1457 
1643 
     | 
    
         
             
                            increment = control['increment'].to_i rescue 0
         
     | 
| 
         @@ -1472,29 +1658,37 @@ class ShowOff < Sinatra::Application 
     | 
|
| 
       1472 
1658 
     | 
    
         | 
| 
       1473 
1659 
     | 
    
         
             
                        when 'register'
         
     | 
| 
       1474 
1660 
     | 
    
         
             
                          # save a list of presenters
         
     | 
| 
       1475 
     | 
    
         
            -
                          if  
     | 
| 
      
 1661 
     | 
    
         
            +
                          if valid_presenter_cookie?
         
     | 
| 
       1476 
1662 
     | 
    
         
             
                            remote = request.env['REMOTE_HOST'] || request.env['REMOTE_ADDR']
         
     | 
| 
       1477 
1663 
     | 
    
         
             
                            settings.presenters << ws
         
     | 
| 
       1478 
1664 
     | 
    
         
             
                            @logger.warn "Registered new presenter: #{remote}"
         
     | 
| 
       1479 
1665 
     | 
    
         
             
                          end
         
     | 
| 
       1480 
1666 
     | 
    
         | 
| 
       1481 
1667 
     | 
    
         
             
                        when 'track'
         
     | 
| 
       1482 
     | 
    
         
            -
                          remote =  
     | 
| 
      
 1668 
     | 
    
         
            +
                          remote = valid_presenter_cookie? ? 'presenter' : request.cookies['client_id']
         
     | 
| 
       1483 
1669 
     | 
    
         
             
                          slide  = control['slide']
         
     | 
| 
       1484 
     | 
    
         
            -
                          time   = control['time'].to_f
         
     | 
| 
       1485 
1670 
     | 
    
         | 
| 
       1486 
     | 
    
         
            -
                           
     | 
| 
       1487 
     | 
    
         
            -
             
     | 
| 
      
 1671 
     | 
    
         
            +
                          if control.has_key? 'time'
         
     | 
| 
      
 1672 
     | 
    
         
            +
                            time = control['time'].to_f
         
     | 
| 
      
 1673 
     | 
    
         
            +
             
     | 
| 
      
 1674 
     | 
    
         
            +
                            # record the UA of the client if we haven't seen it before
         
     | 
| 
      
 1675 
     | 
    
         
            +
                            @@counter['user_agents'][remote] ||= request.user_agent
         
     | 
| 
       1488 
1676 
     | 
    
         | 
| 
       1489 
     | 
    
         
            -
             
     | 
| 
       1490 
     | 
    
         
            -
             
     | 
| 
       1491 
     | 
    
         
            -
             
     | 
| 
       1492 
     | 
    
         
            -
             
     | 
| 
       1493 
     | 
    
         
            -
             
     | 
| 
       1494 
     | 
    
         
            -
             
     | 
| 
       1495 
     | 
    
         
            -
             
     | 
| 
      
 1677 
     | 
    
         
            +
                            views = @@counter['pageviews']
         
     | 
| 
      
 1678 
     | 
    
         
            +
                            # a bucket for this slide
         
     | 
| 
      
 1679 
     | 
    
         
            +
                            views[slide] ||= Hash.new
         
     | 
| 
      
 1680 
     | 
    
         
            +
                            # a bucket of slideviews for this address
         
     | 
| 
      
 1681 
     | 
    
         
            +
                            views[slide][remote] ||= Array.new
         
     | 
| 
      
 1682 
     | 
    
         
            +
                            # and add this slide viewing to the bucket
         
     | 
| 
      
 1683 
     | 
    
         
            +
                            views[slide][remote] << { 'elapsed' => time, 'timestamp' => Time.now.to_i, 'presenter' => @@current[:name] }
         
     | 
| 
      
 1684 
     | 
    
         
            +
             
     | 
| 
      
 1685 
     | 
    
         
            +
                            @logger.debug "Logged #{time} on slide #{slide} for #{remote}"
         
     | 
| 
      
 1686 
     | 
    
         
            +
             
     | 
| 
      
 1687 
     | 
    
         
            +
                          else
         
     | 
| 
      
 1688 
     | 
    
         
            +
                            @@counter['current'][remote] = [slide, Time.now.to_i]
         
     | 
| 
      
 1689 
     | 
    
         
            +
                            @logger.debug "Recorded current slide #{slide} for #{remote}"
         
     | 
| 
      
 1690 
     | 
    
         
            +
                          end
         
     | 
| 
       1496 
1691 
     | 
    
         | 
| 
       1497 
     | 
    
         
            -
                          @logger.debug "Logged #{time} on slide #{slide} for #{remote}"
         
     | 
| 
       1498 
1692 
     | 
    
         | 
| 
       1499 
1693 
     | 
    
         
             
                        when 'position'
         
     | 
| 
       1500 
1694 
     | 
    
         
             
                          ws.send( { 'current' => @@current[:number] }.to_json ) unless @@cookie.nil?
         
     |