From be0dfdea749a51038436dd149da630c443f83325 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Thu, 9 Feb 2012 17:05:12 -0500 Subject: [PATCH] initial commit --- .gitignore | 17 + Gemfile | 4 + LICENSE | 22 ++ README.md | 29 ++ Rakefile | 2 + attentive.gemspec | 34 ++ bin/attentive | 26 ++ lib/assets/javascripts/attentive.js.coffee | 7 + lib/assets/stylesheets/attentive.css.scss | 66 ++++ lib/attentive.rb | 17 + lib/attentive/server.rb | 107 ++++++ lib/attentive/version.rb | 3 + vendor/assets/javascripts/fathom.js | 401 +++++++++++++++++++++ views/index.haml | 2 + views/layout.haml | 11 + 15 files changed, 748 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 attentive.gemspec create mode 100755 bin/attentive create mode 100644 lib/assets/javascripts/attentive.js.coffee create mode 100644 lib/assets/stylesheets/attentive.css.scss create mode 100644 lib/attentive.rb create mode 100644 lib/attentive/server.rb create mode 100644 lib/attentive/version.rb create mode 100644 vendor/assets/javascripts/fathom.js create mode 100644 views/index.haml create mode 100644 views/layout.haml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d4be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d90922d --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in attentive.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87b491e --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 John Bintz + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f4af42 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Attentive + +TODO: Write a gem description + +## Installation + +Add this line to your application's Gemfile: + + gem 'attentive' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install attentive + +## Usage + +TODO: Write usage instructions here + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/attentive.gemspec b/attentive.gemspec new file mode 100644 index 0000000..48288bb --- /dev/null +++ b/attentive.gemspec @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/attentive/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["John Bintz"] + gem.email = ["john@coswellproductions.com"] + gem.description = %q{TODO: Write a gem description} + gem.summary = %q{TODO: Write a gem summary} + gem.homepage = "" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = "attentive" + gem.require_paths = ["lib"] + gem.version = Attentive::VERSION + + gem.add_dependency 'sinatra' + + gem.add_dependency 'sprockets', '~> 2.1.0' + gem.add_dependency 'sprockets-vendor_gems' + gem.add_dependency 'coffee-script' + gem.add_dependency 'sprockets-sass' + gem.add_dependency 'compass', '~> 0.12.rc' + + gem.add_dependency 'haml' + + gem.add_dependency 'nokogiri' + gem.add_dependency 'rdiscount' + gem.add_dependency 'pygments.rb' + + gem.add_dependency 'jquery-rails' +end + diff --git a/bin/attentive b/bin/attentive new file mode 100755 index 0000000..6297d29 --- /dev/null +++ b/bin/attentive @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'bundler' + +begin + Bundler.setup(:default) +rescue Bundler::GemfileNotFound + $: << File.expand_path('../../lib', __FILE__) +end + +require 'rack' +require 'attentive' +require 'pygments' +require 'coffee_script' +require 'sass' + +load 'presentation.rb' + +# make sure pygments is ready +Pygments.highlight("attentive") + +Rack::Handler::WEBrick.run(Attentive::Server, :Port => 9393) do |server| + trap(:INT) { server.shutdown } +end + diff --git a/lib/assets/javascripts/attentive.js.coffee b/lib/assets/javascripts/attentive.js.coffee new file mode 100644 index 0000000..d4051a2 --- /dev/null +++ b/lib/assets/javascripts/attentive.js.coffee @@ -0,0 +1,7 @@ +#= require jquery +#= require fathom + +$(-> + $('#slides').fathom(displayMode: 'multi', scrollLength: 250) +) + diff --git a/lib/assets/stylesheets/attentive.css.scss b/lib/assets/stylesheets/attentive.css.scss new file mode 100644 index 0000000..34c2344 --- /dev/null +++ b/lib/assets/stylesheets/attentive.css.scss @@ -0,0 +1,66 @@ +body, html { + margin: 0; + padding: 0; +} + +#slides { + .slide { + width: 75%; + + .content { + text-align: center; + + width: 100%; + height: 100%; + + h1, h2, h3 { + margin: 0; + } + + h1 { + font-size: 5em; + } + + h2 { + font-size: 3em; + } + + h3 { + font-size: 1.75em; + } + + div.highlight { + text-align: left; + + padding: 1em; + + font-size: 160%; + + pre { + margin: 0; + } + } + } + + &.style-larger { + div.highlight { + font-size: 200%; + } + } + + &.style-even-larger { + div.highlight { + font-size: 250%; + } + } + + &.activeslide { + opacity: 1 !important; + } + + &.inactiveslide { + opacity: 0.4; + } + } +} + diff --git a/lib/attentive.rb b/lib/attentive.rb new file mode 100644 index 0000000..c04c889 --- /dev/null +++ b/lib/attentive.rb @@ -0,0 +1,17 @@ +require "attentive/version" + +module Attentive + autoload :Server, 'attentive/server' + + class << self + attr_accessor :title + end + + def self.configure + yield self + end + + def self.root + Pathname(File.expand_path('../..', __FILE__)) + end +end diff --git a/lib/attentive/server.rb b/lib/attentive/server.rb new file mode 100644 index 0000000..6e366f6 --- /dev/null +++ b/lib/attentive/server.rb @@ -0,0 +1,107 @@ +require 'sprockets' +require 'sprockets-vendor_gems' +require 'sprockets-sass' +require 'compass' +require 'sinatra' +require 'nokogiri' +require 'rdiscount' +require 'pygments' +require 'sinatra/base' + +require 'rack/builder' + +module Attentive + class Server < Rack::Builder + def self.call(env) + @app ||= Rack::Builder.new do + map '/assets' do + env = Sprockets::EnvironmentWithVendoredGems.new + env.append_path 'assets/javascripts' + env.append_path 'assets/stylesheets' + env.append_path Attentive.root.join('vendor/assets/javascripts') + env.append_path Attentive.root.join('lib/assets/javascripts') + env.append_path Attentive.root.join('lib/assets/stylesheets') + + run env + end + + map '/' do + run Attentive::Sinatra + end + end + + @app.call(env) + end + end + + class Sinatra < Sinatra::Base + set :logging, true + + helpers do + def trim_lines(code) + code.lines.collect(&:strip).join("\n") + end + + def slides + highlights = [] + + output = Dir['presentation/*.html'].sort.collect do |file| + xml = Nokogiri::XML("#{File.read(file)}") + + xml.search('slide').collect do |node| + classes = %w{slide} + + if style = node.attributes['style'] + style.to_s.split(' ').each { |s| classes << "style-#{s}" } + end + + node.search('code').collect do |code| + highlighted_code = Pygments.highlight(code.inner_text.strip, :lexer => code.attributes['lang'].to_s) + + code.add_next_sibling("{highlight#{highlights.length}}") + + code.remove + + highlights << highlighted_code + end + + content = node.inner_html + + content = case node.attributes['content'].to_s + when 'html' + content + else + RDiscount.new(trim_lines(content)).to_html + end + + %{
#{content}
} + end.join + end.join + + highlights.each_with_index do |highlight, index| + output.gsub!("{highlight#{index}}", highlight) + end + + output + end + end + + get '/' do + haml :index, :ugly => true + end + + set :views, [ File.join(Dir.pwd, 'views'), Attentive.root.join('views')] + + private + def render(engine, data, options = {}, locals = {}, &block) + settings.views.each do |view| + template = "#{data}.#{engine}" + + if File.file?(File.join(view, template)) + return super(engine, data, options.merge(:views => view), locals, &block) + end + end + end + end +end + diff --git a/lib/attentive/version.rb b/lib/attentive/version.rb new file mode 100644 index 0000000..65c9d00 --- /dev/null +++ b/lib/attentive/version.rb @@ -0,0 +1,3 @@ +module Attentive + VERSION = "0.0.1" +end diff --git a/vendor/assets/javascripts/fathom.js b/vendor/assets/javascripts/fathom.js new file mode 100644 index 0000000..9089f6d --- /dev/null +++ b/vendor/assets/javascripts/fathom.js @@ -0,0 +1,401 @@ +/* +Fathom.js v1.2.2 +Copyright 2011, Mark Dalgleish + +This content is released under the MIT License +github.com/markdalgleish/fathom/blob/master/MIT-LICENSE.txt +*/ + +(function($, window, undefined){ + var Fathom = function(container, options){ + this.container = container; + this.options = options; + + return this.init(); + }, + $window = $(window), + $document = $(document); + + Fathom.prototype = { + defaults: { + portable: undefined, + portableTagName: 'div', + portableClass: 'fathom-container', + displayMode: 'single', + slideTagName: 'div', + slideClass: 'slide', + activeClass: 'activeslide', + inactiveClass: 'inactiveslide', + margin: 100, + onScrollInterval: 300, + scrollLength: 600, + + timeline: undefined, + video: undefined, + + onActivateSlide: undefined, + onDeactivateSlide: undefined + }, + + init: function() { + this.config = $.extend({}, this.defaults, this.options); + + this.$container = $(this.container); + this.$slides = this.$container.find(this.config.slideTagName + + (this.config.slideClass ? '.' + this.config.slideClass : '')); + this.$firstSlide = this.$slides.filter(':first'); + this.$lastSlide = this.$slides.filter(':last'); + this.$activeSlide = this.activateSlide(this.$firstSlide); + + return this + ._detectPortable() + ._setStyles() + ._setClasses() + ._setMargins() + ._setupEvents() + ._setupTimeline() + ._setupVideo() + ._setupScrollHandler(); + }, + + nextSlide: function() { + return this.scrollToSlide(this.$activeSlide.next()); + }, + + prevSlide: function() { + return this.scrollToSlide(this.$activeSlide.prev()); + }, + + scrollToSlide: function($elem) { + var self = this, + $scrollingElement = this.config.portable ? this.$portableContainer : $('html,body'), + $container = this.config.portable ? this.$portableContainer : $window, + portableScrollLeft = this.config.portable ? this.$portableContainer.scrollLeft() : 0; + + if ($elem.length !== 1) { + return $elem; + } + + this.isAutoScrolling = true; + + $scrollingElement.stop().animate({ + scrollLeft: ($elem.position().left + portableScrollLeft - + (($container.width() - $elem.innerWidth()) / 2)) + }, self.config.scrollLength, function() { + self.isAutoScrolling = false; + }); + + return this.activateSlide($elem); + }, + + activateSlide: function($elem) { + var elem = $elem.get(0), + activeSlide; + + if (this.$activeSlide !== undefined) { + activeSlide = this.$activeSlide.get(0) + + if (activeSlide === elem) { + return $elem; + } + + this.$activeSlide.removeClass(this.config.activeClass) + .addClass(this.config.inactiveClass) + .trigger('deactivateslide.fathom'); + + if (typeof this.config.onDeactivateSlide === 'function') { + this.config.onDeactivateSlide.call(activeSlide); + } + } + + $elem.removeClass(this.config.inactiveClass).addClass(this.config.activeClass); + + this.$activeSlide = $elem; + + $elem.trigger('activateslide.fathom'); + + if (typeof this.config.onActivateSlide === 'function') { + this.config.onActivateSlide.call(elem); + } + + return $elem; + }, + + setTime: function( t ) { + var times = this._timeline || []; + for(var i = 0; i < times.length; i++) { + if(times[i].time <= t && times[i+1].time > t) { + if(this.$activeSlide[0] !== times[i].slide[0]) { + this.scrollToSlide( times[i].slide ); + } + break; + } + } + }, + + _detectPortable: function() { + if (this.config.portable === undefined) { + if (this.$container.parent().is('body')) { + this.config.portable = false; + } else { + this.config.portable = true; + } + } + + return this; + }, + + _setupEvents: function() { + var self = this; + + this.$container.delegate(this.config.slideTagName + '.' + this.config.inactiveClass, 'click', function(event) { + event.preventDefault(); + self.scrollToSlide($(this)); + }); + + $document.keydown(function(event) { + var key = event.which; + + if (key === 39 || key === 32) { + event.preventDefault(); + self.nextSlide(); + } else if ( key === 37) { + event.preventDefault(); + self.prevSlide(); + } + }); + + $window.resize(function(){ + self._setMargins(); + }); + + return this; + }, + + _setStyles: function() { + this.$container.css('white-space', 'nowrap'); + + this.$slides.css({ + 'white-space': 'normal', + 'display': 'inline-block', + 'vertical-align': 'top' + }); + + if (this.config.portable) { + this.$portableContainer = $('<' + this.config.portableTagName + ' class="' + this.config.portableClass + '" />'); + this.$container.before(this.$portableContainer).appendTo(this.$portableContainer); + } + + return this; + }, + + _setClasses: function() { + this.$slides.addClass(this.config.inactiveClass); + + this.$activeSlide + .removeClass(this.config.inactiveClass) + .addClass(this.config.inactiveClass); + + return this; + }, + + _getTallestSlideHeight: function() { + var size = 0; + this.$slides.each(function() { + size = Math.max(size, $(this).innerHeight()); + }); + return size; + }, + + _setMargins: function() { + var displayMode = this.config.displayMode, + $container = this.config.portable ? this.$portableContainer : $window, + containerWidth = $container.width(), + tallestSlideHeight = this._getTallestSlideHeight(), + verticalSpacing = Math.ceil(($container.height() - tallestSlideHeight) / 2), + firstSlideSpacing = Math.ceil((containerWidth - this.$firstSlide.innerWidth()) / 2), + lastSlideSpacing = Math.ceil((containerWidth - this.$lastSlide.innerWidth()) / 2), + peekabooWidth = Math.ceil(containerWidth / 25); + + this.$slides.each(function() { + $(this).css('margin-top', Math.ceil(tallestSlideHeight - $(this).innerHeight()) / 2); + }); + + this.$container.css('margin-top', verticalSpacing); + + if (displayMode === 'single') { + this.$slides.css('margin-right', firstSlideSpacing - peekabooWidth); + } else if (displayMode === 'multi') { + this.$slides.css('margin-right', this.config.margin); + } + + this.$firstSlide.css('margin-left', firstSlideSpacing); + this.$lastSlide.css('margin-right', lastSlideSpacing); + + if (this.config.portable) { + var slidesWidth = parseInt(this.$container.css('padding-left')) + parseInt(this.$container.css('padding-right')); + this.$slides.each(function() { + slidesWidth += $(this).outerWidth(true); + }); + this.$container.width(slidesWidth); + } + + return this; + }, + + _setupTimeline: function() { + var slides = this.$slides; + + function parseTime(point) { + for(var m = (point.time || point).toString().match(/(((\d+):)?(\d+):)?(\d+)/), a = 0, i = 3; i <= 5; i++) { + a = (a * 60) + parseInt(m[i] || 0); + } + return a; + } + + var currentSlide = -1; + function parseSlide(point) { + if( point.slide == null ) { + currentSlide++; + } else if($.type(point.slide) === 'number') { + currentSlide = point.slide; + } else{ + for(var match = slides.filter( point.slide )[0], i = 0; i < slides.length; i++ ) { + if( slides[i] === match ) { + currentSlide = i; + break; + } + } + } + return slides.eq( currentSlide ); + } + + if(! this.config.timeline) + return this; + + this._timeline = []; + for(var t = this.config.timeline, i = 0; i < t.length; i++) { + this._timeline.push({ time: parseTime( t[i] ), slide: parseSlide( t[i] ) }); + } + this._timeline.push( { time: 99999, slide: t[0].slide } ); + return this; + }, + + _setupVideo: function() { + if( !this.config.video ) { + this._setupDefaultTimeSource(); + } else if( this.config.video.source === "vimeo" ) { + this._setupVimeoVideo( this.config.video ); + } else { + throw "unknown video source, not supported"; + } + return this; + }, + + _setupDefaultTimeSource: function() { + var self = this, t0 = (new Date()).getTime(); + setInterval(function() { + var t1 = (new Date()).getTime(); + self.setTime( (t1 - t0)/1000 ); + }, 250 ); + }, + + _setupVimeoVideo: function(vid) { + var self = this, vid = this.config.video, downgrade = false; + + if(window.location.protocol === "file:") { + ( "console" in window ) && console.log("vimeo video player api does not work with local files. Downgrading video support\nsee http://vimeo.com/api/docs/player-js"); + downgrade = true; + } + + function loadFrame() { + var id = "p" + vid.id; + var frameSrc = "