diff --git a/attentive.gemspec b/attentive.gemspec index 67ffb42..848210c 100644 --- a/attentive.gemspec +++ b/attentive.gemspec @@ -29,8 +29,6 @@ Gem::Specification.new do |gem| gem.add_dependency 'rdiscount' gem.add_dependency 'pygments.rb' - gem.add_dependency 'jquery-rails' - gem.add_dependency 'thor' end diff --git a/bin/attentive b/bin/attentive index 8a3471d..30de352 100755 --- a/bin/attentive +++ b/bin/attentive @@ -48,6 +48,46 @@ class Attentive::CLI < Thor end end end + + desc "static", "Create a static copy of the site in _site" + def static + urls = [ '/' ] + + Dir['assets/**/*'].each do |file| + if File.file?(file) + parts = file.split('/')[2..-1] + + if !parts.empty? + file = parts.join('/') + + %w{js css}.each do |root| + file.gsub!(%r{([^/]+\.#{root}).*$}, '\1') + end + + urls << File.join('', 'assets', file) + end + end + end + + target_dir = "_site" + + FileUtils.rm_rf target_dir + FileUtils.mkdir_p target_dir + + Attentive.middleware.replace([]) + + urls.each do |url| + response = Attentive::Server.call(Rack::MockRequest.env_for(url)) + + target = "#{target_dir}#{url}" + target += "index.html" if target[-1..-1] == '/' + + puts "Writing #{target}..." + + FileUtils.mkdir_p(File.dirname(target)) + File.open(target, 'wb') { |fh| response.last.each { |part| fh.print part } } + end + end end Attentive::CLI.start diff --git a/lib/assets/javascripts/attentive.js.coffee b/lib/assets/javascripts/attentive.js.coffee index 03e8482..d64b8bf 100644 --- a/lib/assets/javascripts/attentive.js.coffee +++ b/lib/assets/javascripts/attentive.js.coffee @@ -1,13 +1,95 @@ -#= require jquery -#= require fathom +class this.Attentive + @setup: (identifier) -> + starter = -> (new Attentive(identifier)).start() + window.addEventListener('DOMContentLoaded', starter, false) -$(-> - fathom = new Fathom('#slides', displayMode: 'multi', scrollLength: 250) + constructor: (@identifier) -> + @length = @allSlides().length + @priorSlide = null + @initialRender = true - setTimeout( - -> - $(window).trigger('resize') - , 500 - ) -) + bodyClassList: -> + @_bodyClassList ||= document.querySelector('body').classList + + allSlides: -> + @_allSlides ||= @slidesViewer().querySelectorAll('.slide') + + slidesViewer: -> + @_slidesViewer ||= document.querySelector(@identifier) + + start: -> + @bodyClassList().add('loading') + + if !this.isFile() + window.addEventListener('popstate', @handlePopState, false) + + document.addEventListener('click', @handleClick, false) + document.addEventListener('keydown', @handleKeyDown, false) + window.addEventListener('resize', @calculate, false) + + this.advanceTo(this.slideFromLocation()) + + slideFromLocation: -> + value = if this.isFile() + location.hash + else + location.pathname + + Number(value.substr(1)) + + handlePopState: (e) => + this.advanceTo(if e.state then e.state.index else this.slideFromLocation()) + + handleClick: (e) => + this.advance() if e.target.tagName != 'A' + + handleKeyDown: (e) => + switch e.keyCode + when 37 + this.advance(-1) + when 39, 32 + this.advance() + + advance: (offset = 1) => + this.advanceTo(Math.max(Math.min(@currentSlide + offset, @length - 1), 0)) + + isFile: => location.href.slice(0, 4) == 'file' + + advanceTo: (index) => + @priorSlide = @currentSlide + @currentSlide = index || 0 + + this.calculate() + + if this.isFile() + location.hash = @currentSlide + else + history.pushState({ index: @currentSlide }, '', @currentSlide) + + calculate: => + for slide in @allSlides() + slide.style['width'] = "#{window.innerWidth}px" + + height = (window.innerHeight - slide.querySelector('.content').clientHeight) / 2 + + slide.style['marginTop'] = "#{height}px" + + @slidesViewer().style['width'] = "#{window.innerWidth * @allSlides().length}px" + this.align() + + align: => + @allSlides()[@priorSlide].classList.remove('active') if @priorSlide + @allSlides()[@currentSlide].classList.add('active') + + @slidesViewer().style['left'] = "-#{@currentSlide * window.innerWidth}px" + + if @initialRender + @bodyClassList().remove('loading') + + @initialRender = false + + if this.isFile() + setTimeout(this.calculate, 200) + else + this.calculate() diff --git a/lib/assets/stylesheets/attentive.css.scss b/lib/assets/stylesheets/attentive.css.scss index 34c2344..ab48b5f 100644 --- a/lib/assets/stylesheets/attentive.css.scss +++ b/lib/assets/stylesheets/attentive.css.scss @@ -1,17 +1,39 @@ +@import 'compass/utilities'; + body, html { margin: 0; padding: 0; } +#slides-container { + position: absolute; + width: 100%; + height: 100%; + @include clearfix; +} + +body.loading { + #slides-container { + opacity: 0; + } +} + #slides { + position: absolute; + top: 0; + height: 100%; + .slide { - width: 75%; + @include float-left; + position: relative; + height: 100%; .content { text-align: center; - width: 100%; - height: 100%; + position: absolute; + left: 5%; + width: 90%; h1, h2, h3 { margin: 0; @@ -53,14 +75,6 @@ body, html { font-size: 250%; } } - - &.activeslide { - opacity: 1 !important; - } - - &.inactiveslide { - opacity: 0.4; - } } } diff --git a/lib/attentive/server.rb b/lib/attentive/server.rb index 9d56278..c66f2da 100644 --- a/lib/attentive/server.rb +++ b/lib/attentive/server.rb @@ -20,7 +20,7 @@ module Attentive require 'coffee_script' require 'sass' - # make sure pygments is ready + # make sure pygments is ready before starting a new thread Pygments.highlight("attentive") Rack::Handler::WEBrick.run(Attentive::Server, :Port => options[:port]) do |server| @@ -35,7 +35,6 @@ module Attentive env.append_path 'assets/javascripts' env.append_path 'assets/stylesheets' env.append_path 'assets/images' - 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') @@ -143,7 +142,7 @@ module Attentive end end - get '/' do + get %r{/?\d*} do haml :index, :ugly => true end diff --git a/skel/.gitignore b/skel/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/skel/assets/stylesheets/application.css.scss b/skel/assets/stylesheets/application.css.scss index e268f2b..537d4bd 100644 --- a/skel/assets/stylesheets/application.css.scss +++ b/skel/assets/stylesheets/application.css.scss @@ -3,3 +3,14 @@ h1, h2, h3, li, p { font-family: Nunito, sans-serif; } + +#slides { + @include transition-duration(0.5s); + + .slide { + opacity: 0.4; + + &.active { opacity: 1.0 } + } +} + diff --git a/spec/javascripts/attentive_spec.js.coffee b/spec/javascripts/attentive_spec.js.coffee new file mode 100644 index 0000000..e69de29 diff --git a/vendor/assets/javascripts/fathom.js b/vendor/assets/javascripts/fathom.js deleted file mode 100644 index 9089f6d..0000000 --- a/vendor/assets/javascripts/fathom.js +++ /dev/null @@ -1,401 +0,0 @@ -/* -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 = "