adding first revision for deployment

master
Coleman 2008-10-01 01:19:35 -05:00
commit 7812004f04
95 changed files with 9056 additions and 0 deletions

14
README Normal file
View File

@ -0,0 +1,14 @@
BinaryAttraction
A little social program i am writing for my senior design class. This site will
let you vote in the new digital age on photos.
Dependencies:
* Merb
* Haml / Sass
* Merb Helpers
* Merb Has Flash
* Recaptcha account
Released under GNU GPLv2

75
Rakefile Normal file
View File

@ -0,0 +1,75 @@
require 'rubygems'
Gem.clear_paths
Gem.path.unshift(File.join(File.dirname(__FILE__), "gems"))
require 'rake'
require 'rake/rdoctask'
require 'rake/testtask'
require 'spec/rake/spectask'
require 'fileutils'
##
# requires frozen merb-core (from /framework)
# adds the other components to the load path
def require_frozen_framework
framework = File.join(File.dirname(__FILE__), "framework")
if File.directory?(framework)
puts "Running from frozen framework"
core = File.join(framework,"merb-core")
if File.directory?(core)
puts "using merb-core from #{core}"
$:.unshift File.join(core,"lib")
require 'merb-core'
end
more = File.join(framework,"merb-more")
if File.directory?(more)
Dir.new(more).select {|d| d =~ /merb-/}.each do |d|
$:.unshift File.join(more,d,'lib')
end
end
plugins = File.join(framework,"merb-plugins")
if File.directory?(plugins)
Dir.new(plugins).select {|d| d =~ /merb_/}.each do |d|
$:.unshift File.join(plugins,d,'lib')
end
end
require "merb-core/core_ext/kernel"
require "merb-core/core_ext/rubygems"
else
p "merb doesn't seem to be frozen in /framework"
require 'merb-core'
end
end
if ENV['FROZEN']
require_frozen_framework
else
require 'merb-core'
end
require 'merb-core/tasks/merb'
include FileUtils
# Load the basic runtime dependencies; this will include
# any plugins and therefore plugin rake tasks.
init_env = ENV['MERB_ENV'] || 'rake'
Merb.load_dependencies(:environment => init_env)
# Get Merb plugins and dependencies
Merb::Plugins.rakefiles.each { |r| require r }
# Load any app level custom rakefile extensions from lib/tasks
tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks")
rake_files = Dir["#{tasks_path}/*.rake"]
rake_files.each{|rake_file| load rake_file }
desc "start runner environment"
task :merb_env do
Merb.start_environment(:environment => init_env, :adapter => 'runner')
end
##############################################################################
# ADD YOUR CUSTOM TASKS IN /lib/tasks
# NAME YOUR RAKE FILES file_name.rake
##############################################################################

View File

@ -0,0 +1,9 @@
class Application < Merb::Controller
def current_user
(@user ||= User.find(session[:user_id]))
end
def reset_session
session[:user_id] = nil
end
end

View File

@ -0,0 +1,13 @@
class Exceptions < Application
# handle NotFound exceptions (404)
def not_found
render :format => :html
end
# handle NotAcceptable exceptions (406)
def not_acceptable
render :format => :html
end
end

View File

@ -0,0 +1,9 @@
class Favorites < Application
# ...and remember, everything returned from an action
# goes to the client...
def index
render
end
end

9
app/controllers/home.rb Normal file
View File

@ -0,0 +1,9 @@
class Home < Application
def index
render
end
def acceptable_use
render
end
end

View File

@ -0,0 +1,9 @@
class Photos < Application
# ...and remember, everything returned from an action
# goes to the client...
def index
render
end
end

View File

@ -0,0 +1,27 @@
class Sessions < Application
def index
redirect '/'
end
def new
render
end
def create
user = User.find_by_user_name params[:user_name]
if user.authenticated_against?(params[:password])
session[:user_id] = user.id
flash[:notice] = 'Great success!'
redirect '/'
else
flash[:error] = 'Login failed'
render :new
end
end
def delete
reset_session
flash[:notice] = 'Goodbye!'
redirect '/'
end
end

9
app/controllers/stats.rb Normal file
View File

@ -0,0 +1,9 @@
class Stats < Application
# ...and remember, everything returned from an action
# goes to the client...
def index
render
end
end

71
app/controllers/users.rb Normal file
View File

@ -0,0 +1,71 @@
class Users < Application
before :prepare_user, :only => [ :show, :edit, :update, :delete ]
include Ambethia::ReCaptcha::Controller
def index
if current_user.administrator?
@users = User.find :all, :order => 'user_name ASC'
render
else
redirect url(:user, :id => current_user.user_name)
end
end
def show
render
end
def new
@user = User.new
render
end
def create
@user = User.new params[:user]
@user.user_name = params[:user][:user_name] rescue nil
if verify_recaptcha(@user) and @user.save
flash[:notice] = 'Great success'
redirect '/'
else
flash[:error] = 'The user could not be created...'
render :new
end
end
def edit
render
end
def update
if @user.save
flash[:notice] = 'Great success'
redirect url(:users)
else
render :edit
end
end
def delete
if @user.destroy
flash[:notice] = "Epic failure, goodbye #{@user.user_name}"
reset_session if @user.id == session[:user_id]
else
flash[:error] = 'That does not work...'
end
redirect url(:users)
end
protected
def prepare_user
@user = if current_user.administrator?
User.find_by_user_name params[:id]
else
current_user
end
raise NotFound if @user.nil?
@user.attributes = params[:user] if params[:user] and request.post?
true
end
end

9
app/controllers/votes.rb Normal file
View File

@ -0,0 +1,9 @@
class Votes < Application
# ...and remember, everything returned from an action
# goes to the client...
def index
render
end
end

View File

@ -0,0 +1,5 @@
module Merb
module FavoritesHelper
end
end # Merb

View File

@ -0,0 +1,15 @@
module Merb
module GlobalHelpers
def logged_in?
!session[:user_id].nil?
end
def error_messages(obj)
if obj.errors.empty?
nil
else
"<div id='model_errors'><ul>#{obj.errors.each_full { |msg| "<li>#{msg}</li>" }}</ul></div>"
end
end
end
end

View File

@ -0,0 +1,5 @@
module Merb
module HomeHelper
end
end # Merb

View File

@ -0,0 +1,5 @@
module Merb
module PhotosHelper
end
end # Merb

View File

@ -0,0 +1,5 @@
module Merb
module SessionHelper
end
end # Merb

View File

@ -0,0 +1,5 @@
module Merb
module StatsHelper
end
end # Merb

View File

@ -0,0 +1,5 @@
module Merb
module UsersHelper
include Ambethia::ReCaptcha::Helper
end
end # Merb

View File

@ -0,0 +1,5 @@
module Merb
module VotesHelper
end
end # Merb

View File

@ -0,0 +1,9 @@
module Merb
module Session
# The Merb::Session module gets mixed into Merb::SessionContainer to allow
# app-level functionality; it will be included and methods will be available
# through request.session as instance methods.
end
end

8
app/models/photo.rb Normal file
View File

@ -0,0 +1,8 @@
class Photo < ActiveRecord::Base
#property :id, Integer, :serial => true
#property :filename, String
#property :email_hash, String
#property :created_at, DateTime
validates_presence_of :filename
has_many :votes, :dependent => :destroy
end

44
app/models/user.rb Normal file
View File

@ -0,0 +1,44 @@
class User < ActiveRecord::Base
attr_accessor :password, :password_confirmation
attr_protected :user_name
attr_protected :auth_token
attr_protected :authorized
validates_presence_of :user_name
validates_length_of :user_name, :within => (6..32)
validates_uniqueness_of :user_name
validates_format_of :user_name, :with => /[\w_-]+/
has_many :votes, :dependent => :destroy
before_validation :saltify_password
def authenticated_against?(str)
ss = User.salted_string(str)
if self.auth_token.to_s == ss
true
else
false
end
end
def self.salted_string(str)
Digest::SHA1.hexdigest("#{Merb::Config[:session_secret_key]}--#{str}--")
end
protected
def saltify_password
if !self.password.to_s.empty?
if self.password.to_s.size < 6
self.errors.add(:password, 'is too short')
elsif self.password != self.password_confirmation
self.errors.add(:passwords, 'do not match')
else
self.auth_token = User.salted_string(self.password)
end
elsif self.auth_token.to_s.empty?
self.errors.add(:password, 'is missing')
end
end
end

34
app/models/vote.rb Normal file
View File

@ -0,0 +1,34 @@
class Vote < ActiveRecord::Base
belongs_to :photo
belongs_to :user
validates_presence_of :vote
##
# Checks if this vote is anonymous, or not an authenticated User vote.
#
def anonymous?
self.user_id.nil?
end
##
# Convert this Vote to a number.
#
def to_i
self.vote? ? 1 : 0
end
##
# Is this a 'no' vote.
#
def zero?
self.to_i == 0
end
##
# Is this a 'yes' vote.
#
def one?
self.to_i == 1
end
end

View File

@ -0,0 +1,216 @@
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title><%= @exception_name %></title>
<style type="text/css" media="screen">
body {
font-family:arial;
font-size:11px;
}
h1 {
font-size:48px;
letter-spacing:-4px;
margin:0;
line-height:36px;
color:#333;
}
h1 sup {
font-size: 0.5em;
}
h1 sup.error_500, h1 sup.error_400 {
color:#990E05;
}
h1 sup.error_100, h1 sup.error_200 {
color:#00BF10;
}
h1 sup.error_300 {
/* pretty sure you cant 'see' status 300
errors but if you could I think they
would be blue */
color:#1B2099;
}
h2 {
font-size:36px;
letter-spacing:-3px;
margin:0;
line-height:28px;
color:#444;
}
a, a:visited {
color:#00BF10;
}
.internalError {
width:800px;
margin:50px auto;
}
.header {
border-bottom:10px solid #333;
margin-bottom:1px;
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
padding:20px;
}
table.trace {
width:100%;
font-family:courier, monospace;
letter-spacing:-1px;
border-collapse: collapse;
border-spacing:0;
}
table.trace tr td{
padding:0;
height:26px;
font-size:13px;
vertical-align:middle;
}
table.trace tr.file{
border-top:2px solid #fff;
background-color:#F3F3F3;
}
table.trace tr.source {
background-color:#F8F8F8;
display:none;
}
table.trace .open tr.source {
display:table-row;
}
table.trace tr.file td.expand {
width:23px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
background-position:top left;
background-repeat:no-repeat;
}
table.trace .open tr.file td.expand {
width:19px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
background-position:top left;
background-repeat:no-repeat;
}
table.trace tr.source td.collapse {
width:19px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
background-position:bottom left;
background-repeat:no-repeat;
background-color:#6F706F;
}
table.trace tr td.path {
padding-left:10px;
}
table.trace tr td.code {
padding-left:35px;
white-space: pre;
line-height:9px;
padding-bottom:10px;
}
table.trace tr td.code em {
font-weight:bold;
color:#00BF10;
}
table.trace tr td.code a {
width: 20px;
float: left;
}
table.trace tr td.code .more {
color:#666;
}
table.trace tr td.line {
width:30px;
text-align:right;
padding-right:4px;
}
.footer {
margin-top:5px;
font-size:11px;
color:#444;
text-align:right;
}
</style>
</head>
<body>
<div class="internalError">
<div class="header">
<h1><%= @exception_name %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
<% if show_details = ::Merb::Config[:exception_details] -%>
<h2><%=h @exception.message %></h2>
<% else -%>
<h2>Sorry about that...</h2>
<% end -%>
<h3>Parameters</h3>
<ul>
<% params[:original_params].each do |param, value| %>
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
<% end %>
<%= "<li>None</li>" if params[:original_params].empty? %>
</ul>
<h3>Session</h3>
<ul>
<% params[:original_session].each do |param, value| %>
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
<% end %>
<%= "<li>None</li>" if params[:original_session].empty? %>
</ul>
<h3>Cookies</h3>
<ul>
<% params[:original_cookies].each do |param, value| %>
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
<% end %>
<%= "<li>None</li>" if params[:original_cookies].empty? %>
</ul>
</div>
<% if show_details %>
<table class="trace">
<% @exception.backtrace.each_with_index do |line, index| %>
<tbody class="close">
<tr class="file">
<td class="expand">
</td>
<td class="path">
<%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>
<% unless line.match(/\.erb:/) %>
in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
<% else %>
(<strong>ERB Template</strong>)
<% end %>
</td>
<td class="line">
<a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&amp;line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>&nbsp;
</td>
</tr>
<tr class="source">
<td class="collapse">
</td>
<td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
<a href="txmt://open?url=file://<%=file%>&amp;line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? CGI.escapeHTML(lcode[0..90])+'<span class="more">......</span>' : CGI.escapeHTML(lcode) %><%='</em>' if llineno==lineno.to_i %>
<% end %>
</td>
</tr>
</tbody>
<% end %>
</table>
<script type="text/javascript" charset="utf-8">
// swop the open & closed classes
els = document.getElementsByTagName('td');
for(i=0; i<els.length; i++){
if(els[i].className=='expand' || els[i].className=='collapse'){
els[i].onclick = function(e){
tbody = this.parentNode.parentNode;
if(tbody.className=='open'){
tbody.className='closed';
}else{
tbody.className='open';
}
}
}
}
</script>
<% end %>
<div class="footer">
lots of love, from <a href="#">merb</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,63 @@
<div id="container">
<div id="header-container">
<img src="/images/merb.jpg" />
<!-- <h1>Mongrel + Erb</h1> -->
<h2>pocket rocket web framework</h2>
<hr />
</div>
<div id="left-container">
<h3>Exception:</h3>
<p><%= params[:exception] %></p>
</div>
<div id="main-container">
<h3>Why am I seeing this page?</h3>
<p>Merb couldn't find an appropriate content_type to return,
based on what you said was available via provides() and
what the client requested.</p>
<h3>How to add a mime-type</h3>
<pre><code>
Merb.add_mime_type :pdf, :to_pdf, %w[application/pdf], &quot;Content-Encoding&quot; =&gt; &quot;gzip&quot;
</code></pre>
<h3>What this means is:</h3>
<ul>
<li>Add a mime-type for :pdf</li>
<li>Register the method for converting objects to PDF as <code>#to_pdf</code>.</li>
<li>Register the incoming mime-type "Accept" header as <code>application/pdf</code>.</li>
<li>Specify a new header for PDF types so it will set <code>Content-Encoding</code> to gzip.</li>
</ul>
<h3>You can then do:</h3>
<pre><code>
class Foo &lt; Application
provides :pdf
end
</code></pre>
<h3>Where can I find help?</h3>
<p>If you have any questions or if you can't figure something out, please take a
look at our <a href="http://merbivore.com/"> project page</a>,
feel free to come chat at irc.freenode.net, channel #merb,
or post to <a href="http://groups.google.com/group/merb">merb mailing list</a>
on Google Groups.</p>
<h3>What if I've found a bug?</h3>
<p>If you want to file a bug or make your own contribution to Merb,
feel free to register and create a ticket at our
<a href="http://merb.lighthouseapp.com/">project development page</a>
on Lighthouse.</p>
<h3>How do I edit this page?</h3>
<p>You can change what people see when this happens by editing <tt>app/views/exceptions/not_acceptable.html.erb</tt>.</p>
</div>
<div id="footer-container">
<hr />
<div class="left"></div>
<div class="right">&copy; 2007 the merb dev team</div>
<p>&nbsp;</p>
</div>
</div>

View File

@ -0,0 +1,47 @@
<div id="container">
<div id="header-container">
<img src="/images/merb.jpg" />
<!-- <h1>Mongrel + Erb</h1> -->
<h2>pocket rocket web framework</h2>
<hr />
</div>
<div id="left-container">
<h3>Exception:</h3>
<p><%= params[:exception] %></p>
</div>
<div id="main-container">
<h3>Welcome to Merb!</h3>
<p>Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.</p>
<h3>Where can I find help?</h3>
<p>If you have any questions or if you can't figure something out, please take a
look at our <a href="http://merbivore.com/"> project page</a>,
feel free to come chat at irc.freenode.net, channel #merb,
or post to <a href="http://groups.google.com/group/merb">merb mailing list</a>
on Google Groups.</p>
<h3>What if I've found a bug?</h3>
<p>If you want to file a bug or make your own contribution to Merb,
feel free to register and create a ticket at our
<a href="http://merb.lighthouseapp.com/">project development page</a>
on Lighthouse.</p>
<h3>How do I edit this page?</h3>
<p>You're seeing this page because you need to edit the following files:
<ul>
<li>config/router.rb <strong><em>(recommended)</em></strong></li>
<li>app/views/exceptions/not_found.html.erb <strong><em>(recommended)</em></strong></li>
<li>app/views/layout/application.html.erb <strong><em>(change this layout)</em></strong></li>
</ul>
</p>
</div>
<div id="footer-container">
<hr />
<div class="left"></div>
<div class="right">&copy; 2007 the merb dev team</div>
<p>&nbsp;</p>
</div>
</div>

View File

@ -0,0 +1 @@
You're in index of the Favorites controller.

View File

@ -0,0 +1,12 @@
%h1 Acceptable use policy
%p There are a few rules you must abide by to use this site. Violation will at least mean a permenant ban and at worst prosecution under federal law.
%ol
%li You must be 18 years old.
%li The pictures must be of a person. One <em>consenting adult</em> person.
%li Group pictures are allowed on the grounds that you are voting for <strong>the entire group</strong>.
%li Nudity is not allowed. Partial nudity is allowed. We will use any discretionary method to determine what is allowed. Be warned.
%li Always offer to send a statistics link to the person you are photographing. It's just polite :)
%p We will <strong>never</strong> share any personal information gathered on this site. The only information we gather is so that <em>you</em>, the user, will have access to the data later.

View File

@ -0,0 +1,61 @@
%style{ :type => 'text/css' }
:sass
#front_page
blockquote
:font-size 16px
:padding 5px 0px 5px 15px
:background-color #eeeeee
:border-top 1px dashed #9f9f9f
:border-right 1px dashed #9f9f9f
:border-bottom 1px solid #d4d4d4
:border-left 1px solid #d4d4d4
em
:margin-left 20px
h1
:text-align center
%span#front_page
%h1 What this is all about
%blockquote
All you young guys are on a binary system. It's either <tt>0</tt> or <tt>1</tt>.
%br
%em Larry Bell
%blockquote
All you old guys are on the analog system. Join the digital revolution.
%br
%em Ross Bagwell
%h1 Getting started
%ul.no_list_style
- if logged_in?
%li
%a{ :href => url(:edit_user, :id => current_user.user_name) }
%img{ :src => '/images/system-lock-screen.png' }
Change your password
%li
%a{ :href => url(:favorite, :id => current_user.user_name) }
%img{ :src => '/images/emblem-favorite.png' }
Check your favorites
%li
%a{ :href => url(:new_photo) }
%img{ :src => '/images/camera-photo.png' }
Upload photos
%li
%a{ :href => url(:votes) }
%img{ :src => '/images/vote.png' }
Vote on new photos
%li
%a{ :href => url(:stat, :id => current_user.user_name) }
%img{ :src => '/images/utilities-system-monitor.png' }
Check stats on photos of yourself
- else
%li <img src='/images/system-users.png' /> Sign up for an account
%li <img src='/images/system-lock-screen.png' /> Log in if you have one
%li <img src='/images/emblem-favorite.png' /> Check your favorites
%li <img src='/images/camera-photo.png' /> Upload photos
%li <img src='/images/vote.png' /> Vote on new photos
%li <img src='/images/utilities-system-monitor.png' /> Check stats on photos of yourself

View File

@ -0,0 +1,59 @@
!!! Strict
%html{html_attrs{'en-us'}}
%head
%title Binary Attraction
%meta{ 'http-equiv' => "content-type", :content => "text/html; charset=utf8" }
%link{ :href => "/stylesheets/ba.css", :rel => "stylesheet", :type => "text/css", :media => "screen", :charset => "utf-8" }
%script{ :src => "/javascripts/prototype.js", :type => "text/javascript" }
%script{ :src => "/javascripts/effects.js", :type => "text/javascript" }
%script{ :src => "/javascripts/dragdrop.js", :type => "text/javascript" }
%script{ :src => "/javascripts/application.js", :type => "text/javascript" }
- unless flash.keys.empty?
:javascript
hide_flashes();
%body
#container
- unless flash.keys.empty?
#flash_container
- flash.keys.each do |key|
%div{ :class => key }= flash[key]
#header
%span#header_image
%a{ :href => '/', :title => 'B.A. Home' }
%img{ :src => '/images/binaryattraction.png', :alt => 'Binary Attraction' }
#tool_bar
- if logged_in?
%a{ :href => url(:new_vote), :title => 'Vote' }
%img{ :src => '/images/vote.png' }
Vote
|
%a{ :href => url(:new_photo), :title => 'Upload a photo' }
%img{ :src => '/images/camera-photo.png' }
Upload a photo
|
%a{ :href => url(:favorite, :id => session[:user_id]), :title => 'Favorites' }
%img{ :src => '/images/emblem-favorite.png' }
Favorites
|
%a{ :href => url(:stats), :title => 'Stats' }
%img{ :src => '/images/utilities-system-monitor.png' }
Check Stats
|
%a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out' }
%img{ :src => '/images/system-log-out.png' }
Log out
- else
%a{ :href => url(:new_user), :title => 'Sign Up' }
%img{ :src => '/images/system-users.png' }
Sign Up
|
%a{ :href => url(:new_session), :title => 'Log In' }
%img{ :src => '/images/system-lock-screen.png' }
Log In
#content
= catch_content :for_layout
#footer
&copy; 2008
%a{ :href => 'http://penguincoder.org' } penguincoder
| Usage of this site requires
%a{ :href => '/acceptable_use' } acceptable use policy

View File

@ -0,0 +1 @@
You're in index of the Photos controller.

View File

@ -0,0 +1,12 @@
= form :action => url(:sessions) do
%fieldset
%p
%label{ :for => 'user_name' }
User Name
= text_field :name => 'user_name', :id => 'user_name'
%p
%label{ :for => 'password' }
Password
= password_field :name => 'password', :id => 'password'
= submit 'Login'

View File

@ -0,0 +1 @@
You're in index of the Stats controller.

View File

@ -0,0 +1,12 @@
= error_messages @user
= form :action => url(:edit_user, :id => @user.user_name) do
%fieldset
%legend== Changing settings for user <em>#{@user.user_name}</em>
%p
%label{ :for => 'password' } Password
= password_field :name => 'user[password]', :id => 'password'
%p
%label{ :for => 'password_confirmation' } Confirm
= password_field :name => 'user[password]', :id => 'password'
= submit 'Save'

View File

@ -0,0 +1,9 @@
%h1 Peoples.
%ul
- @users.each do |user|
%li
%a{ :href => url(:edit_user, :id => user.user_name) }= user.user_name
%a{ :href => url(:delete_user, :id => user.user_name), :onclick => "return confirm('Are you sure?');" }
%img{ :src => '/images/user-trash.png' }

View File

@ -0,0 +1,18 @@
= error_messages @user
= form_for @user do
%fieldset
%legend Create a new user
%p
%label{ :for => 'user_name' } User Name
= text_field :name => 'user[user_name]', :id => 'user_name'
%p
%label{ :for => 'password' } Password
= password_field :name => 'user[password]', :id => 'password'
%p
%label{ :for => 'password_confirmation' } Confirm
= password_field :name => 'user[password_confirmation]', :id => 'password_confirmation'
%p
%label{ :for => 'recaptcha_response_field' } Captcha
#recaptcha_container= recaptcha_tags
= submit 'Create'

View File

@ -0,0 +1 @@
You're in index of the Votes controller.

1
autotest/discover.rb Normal file
View File

@ -0,0 +1 @@
Autotest.add_discovery { "merb" }

149
autotest/merb.rb Normal file
View File

@ -0,0 +1,149 @@
# Adapted from Autotest::Rails
require 'autotest'
class Autotest::Merb < Autotest
# +model_tests_dir+:: the directory to find model-centric tests
# +controller_tests_dir+:: the directory to find controller-centric tests
# +view_tests_dir+:: the directory to find view-centric tests
# +fixtures_dir+:: the directory to find fixtures in
attr_accessor :model_tests_dir, :controller_tests_dir, :view_tests_dir, :fixtures_dir
def initialize
super
initialize_test_layout
# Ignore any happenings in these directories
add_exception %r%^\./(?:doc|log|public|tmp)%
# Ignore any mappings that Autotest may have already set up
clear_mappings
# Any changes to a file in the root of the 'lib' directory will run any
# model test with a corresponding name.
add_mapping %r%^lib\/.*\.rb% do |filename, _|
files_matching Regexp.new(["^#{model_test_for(filename)}$"])
end
# Any changes to a fixture will run corresponding view, controller and
# model tests
add_mapping %r%^#{fixtures_dir}/(.*)s.yml% do |_, m|
[
model_test_for(m[1]),
controller_test_for(m[1]),
view_test_for(m[1])
]
end
# Any change to a test or test will cause it to be run
add_mapping %r%^test/(unit|models|integration|controllers|views|functional)/.*rb$% do |filename, _|
filename
end
# Any change to a model will cause it's corresponding test to be run
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
model_test_for(m[1])
end
# Any change to the global helper will result in all view and controller
# tests being run
add_mapping %r%^app/helpers/global_helpers.rb% do
files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$%
end
# Any change to a helper will run it's corresponding view and controller
# tests, unless the helper is the global helper. Changes to the global
# helper run all view and controller tests.
add_mapping %r%^app/helpers/(.*)_helper(s)?.rb% do |_, m|
if m[1] == "global" then
files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$%
else
[
view_test_for(m[1]),
controller_test_for(m[1])
]
end
end
# Changes to views result in their corresponding view and controller test
# being run
add_mapping %r%^app/views/(.*)/% do |_, m|
[
view_test_for(m[1]),
controller_test_for(m[1])
]
end
# Changes to a controller result in its corresponding test being run. If
# the controller is the exception or application controller, all
# controller tests are run.
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
if ["application", "exception"].include?(m[1])
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
else
controller_test_for(m[1])
end
end
# If a change is made to the router, run all controller and view tests
add_mapping %r%^config/router.rb$% do # FIX
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
end
# If any of the major files governing the environment are altered, run
# everything
add_mapping %r%^test/test_helper.rb|config/(init|rack|environments/test.rb|database.yml)% do # FIX
files_matching %r%^test/(unit|models|controllers|views|functional)/.*_test\.rb$%
end
end
private
# Determines the paths we can expect tests or specs to reside, as well as
# corresponding fixtures.
def initialize_test_layout
self.model_tests_dir = "test/unit"
self.controller_tests_dir = "test/functional"
self.view_tests_dir = "test/views"
self.fixtures_dir = "test/fixtures"
end
# Given a filename and the test type, this method will return the
# corresponding test's or spec's name.
#
# ==== Arguments
# +filename+<String>:: the file name of the model, view, or controller
# +kind_of_test+<Symbol>:: the type of test we that we should run
#
# ==== Returns
# String:: the name of the corresponding test or spec
#
# ==== Example
#
# > test_for("user", :model)
# => "user_test.rb"
# > test_for("login", :controller)
# => "login_controller_test.rb"
# > test_for("form", :view)
# => "form_view_spec.rb" # If you're running a RSpec-like suite
def test_for(filename, kind_of_test)
name = [filename]
name << kind_of_test.to_s if kind_of_test == :view
name << "test"
return name.join("_") + ".rb"
end
def model_test_for(filename)
[model_tests_dir, test_for(filename, :model)].join("/")
end
def controller_test_for(filename)
[controller_tests_dir, test_for(filename, :controller)].join("/")
end
def view_test_for(filename)
[view_tests_dir, test_for(filename, :view)].join("/")
end
end

165
autotest/merb_rspec.rb Normal file
View File

@ -0,0 +1,165 @@
# Adapted from Autotest::Rails, RSpec's autotest class, as well as merb-core's.
require 'autotest'
class RspecCommandError < StandardError; end
# This class maps your application's structure so Autotest can understand what
# specs to run when files change.
#
# Fixtures are _not_ covered by this class. If you change a fixture file, you
# will have to run your spec suite manually, or, better yet, provide your own
# Autotest map explaining how your fixtures are set up.
class Autotest::MerbRspec < Autotest
def initialize
super
# Ignore any happenings in these directories
add_exception %r%^\./(?:doc|log|public|tmp|\.git|\.hg|\.svn|framework|gems|schema|\.DS_Store|autotest|bin|.*\.sqlite3)%
# Ignore SCM directories and custom Autotest mappings
%w[.svn .hg .git .autotest].each { |exception| add_exception(exception) }
# Ignore any mappings that Autotest may have already set up
clear_mappings
# Anything in /lib could have a spec anywhere, if at all. So, look for
# files with roughly the same name as the file in /lib
add_mapping %r%^lib\/(.*)\.rb% do |_, m|
files_matching %r%^spec\/#{m[1]}%
end
add_mapping %r%^spec/(spec_helper|shared/.*)\.rb$% do
all_specs
end
# Changing a spec will cause it to run itself
add_mapping %r%^spec/.*\.rb$% do |filename, _|
filename
end
# Any change to a model will cause it's corresponding test to be run
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
spec_for(m[1], 'model')
end
# Any change to global_helpers will result in all view and controller
# tests being run
add_mapping %r%^app/helpers/global_helpers\.rb% do
files_matching %r%^spec/(views|controllers|helpers)/.*_spec\.rb$%
end
# Any change to a helper will cause its spec to be run
add_mapping %r%^app/helpers/((.*)_helper(s)?)\.rb% do |_, m|
spec_for(m[1], 'helper')
end
# Changes to a view cause its spec to be run
add_mapping %r%^app/views/(.*)/% do |_, m|
spec_for(m[1], 'view')
end
# Changes to a controller result in its corresponding spec being run. If
# the controller is the exception or application controller, all
# controller specs are run.
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
if ["application", "exception"].include?(m[1])
files_matching %r%^spec/controllers/.*_spec\.rb$%
else
spec_for(m[1], 'controller')
end
end
# If a change is made to the router, run controller, view and helper specs
add_mapping %r%^config/router.rb$% do
files_matching %r%^spec/(controllers|views|helpers)/.*_spec\.rb$%
end
# If any of the major files governing the environment are altered, run
# everything
add_mapping %r%^config/(init|rack|environments/test).*\.rb|database\.yml% do
all_specs
end
end
def failed_results(results)
results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m)
end
def handle_results(results)
@failures = failed_results(results)
@files_to_test = consolidate_failures(@failures)
@files_to_test.empty? && !$TESTING ? hook(:green) : hook(:red)
@tainted = !@files_to_test.empty?
end
def consolidate_failures(failed)
filters = Hash.new { |h,k| h[k] = [] }
failed.each do |spec, failed_trace|
if f = test_files_for(failed).find { |f| f =~ /spec\// }
filters[f] << spec
break
end
end
filters
end
def make_test_cmd(specs_to_runs)
[
ruby,
"-S",
spec_command,
add_options_if_present,
files_to_test.keys.flatten.join(' ')
].join(' ')
end
def add_options_if_present
File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
end
# Finds the proper spec command to use. Precendence is set in the
# lazily-evaluated method spec_commands. Alias + Override that in
# ~/.autotest to provide a different spec command then the default
# paths provided.
def spec_command(separator=File::ALT_SEPARATOR)
unless defined?(@spec_command)
@spec_command = spec_commands.find { |cmd| File.exists?(cmd) }
raise RspecCommandError, "No spec command could be found" unless @spec_command
@spec_command.gsub!(File::SEPARATOR, separator) if separator
end
@spec_command
end
# Autotest will look for spec commands in the following
# locations, in this order:
#
# * default spec bin/loader installed in Rubygems
# * any spec command found in PATH
def spec_commands
[File.join(Config::CONFIG['bindir'], 'spec'), 'spec']
end
private
# Runs +files_matching+ for all specs
def all_specs
files_matching %r%^spec/.*_spec\.rb$%
end
# Generates a path to some spec given its kind and the match from a mapping
#
# ==== Arguments
# match<String>:: the match from a mapping
# kind<String>:: the kind of spec that the match represents
#
# ==== Returns
# String
#
# ==== Example
# > spec_for('post', :view')
# => "spec/views/post_spec.rb"
def spec_for(match, kind)
File.join("spec", kind + 's', "#{match}_spec.rb")
end
end

View File

@ -0,0 +1,8 @@
Merb.logger.info("Loaded DEVELOPMENT Environment...")
Merb::Config.use { |c|
c[:exception_details] = true
c[:reload_classes] = true
c[:reload_time] = 0.5
c[:log_auto_flush ] = true
c[:log_level] = :debug
}

View File

@ -0,0 +1,7 @@
Merb.logger.info("Loaded PRODUCTION Environment...")
Merb::Config.use { |c|
c[:exception_details] = false
c[:reload_classes] = false
c[:log_level] = :error
c[:log_file] = Merb.log_path + "/production.log"
}

View File

@ -0,0 +1,7 @@
Merb.logger.info("Loaded RAKE Environment...")
Merb::Config.use { |c|
c[:exception_details] = true
c[:reload_classes] = false
c[:log_auto_flush ] = true
c[:log_file] = Merb.log_path / 'merb_rake.log'
}

View File

@ -0,0 +1,6 @@
Merb.logger.info("Loaded TEST Environment...")
Merb::Config.use { |c|
c[:testing] = true
c[:exception_details] = true
c[:log_auto_flush ] = true
}

28
config/init.rb Normal file
View File

@ -0,0 +1,28 @@
Gem.clear_paths
Gem.path.unshift(Merb.root / "gems")
$LOAD_PATH.unshift(Merb.root / "lib")
# Merb.push_path(:lib, Merb.root / "lib") # uses **/*.rb as path glob
dependencies 'haml', 'sass', 'merb_helpers', 'merb_has_flash', 'digest/sha1', 'recaptcha'
Merb::BootLoader.after_app_loads do
recaptcha_path = File.join(Merb.root, 'config', 'recaptcha.yml')
if File.file?(recaptcha_path) and File.readable?(recaptcha_path)
rc = YAML::load_file(recaptcha_path)
ENV['RECAPTCHA_PUBLIC_KEY'] = rc[:public]
ENV['RECAPTCHA_PRIVATE_KEY'] = rc[:private]
else
raise "ReCaptcha configuration file not found!"
end
end
use_orm :activerecord
use_test :rspec
use_template_engine :haml
Merb::Config.use do |c|
c[:session_secret_key] = 'ccf75249b0efbdb3edff96d0a1b16b19cf91f31e'
c[:session_store] = :activerecord
c[:sass] ||= {}
c[:sass][:style] = :compact
end

12
config/rack.rb Normal file
View File

@ -0,0 +1,12 @@
# use PathPrefix Middleware if :path_prefix is set in Merb::Config
if prefix = ::Merb::Config[:path_prefix]
use Merb::Rack::PathPrefix, prefix
end
# comment this out if you are running merb behind a load balancer
# that serves static files
use Merb::Rack::Static, Merb.dir_for(:public)
# this is our main merb application
run Merb::Rack::Application.new

View File

@ -0,0 +1,3 @@
---
:public: PUBKEY
:private: PRIVKEY

13
config/router.rb Normal file
View File

@ -0,0 +1,13 @@
Merb.logger.info("Compiling routes...")
Merb::Router.prepare do |r|
r.resources :sessions
r.resources :users
r.resources :people
r.resources :votes
r.resources :photos
r.resources :favorites
r.resources :stats
# r.default_routes
r.match('/').to( :controller => 'home', :action => 'index' )
r.match('/acceptable_use').to( :controller => 'home', :action => 'acceptable_use' )
end

19
lib/LICENSE_for_recaptcha Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2007 Jason L Perry
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.

78
lib/recaptcha.rb Normal file
View File

@ -0,0 +1,78 @@
# ReCAPTCHA
module Ambethia
module ReCaptcha
RECAPTCHA_API_SERVER = 'http://api.recaptcha.net';
RECAPTCHA_API_SECURE_SERVER = 'https://api-secure.recaptcha.net';
RECAPTCHA_VERIFY_SERVER = 'api-verify.recaptcha.net';
SKIP_VERIFY_ENV = ['test']
module Helper
# Your public API can be specified in the +options+ hash or preferably the environment
# variable +RECAPTCHA_PUBLIC_KEY+.
def recaptcha_tags(options = {})
# Default options
key = options[:public_key] ||= ENV['RECAPTCHA_PUBLIC_KEY']
error = options[:error] ||= session[:recaptcha_error]
uri = options[:ssl] ? RECAPTCHA_API_SECURE_SERVER : RECAPTCHA_API_SERVER
xhtml = Builder::XmlMarkup.new :target => out=(''), :indent => 2 # Because I can.
if options[:display]
xhtml.script(:type => "text/javascript"){ xhtml.text! "var RecaptchaOptions = #{options[:display].to_json};\n"}
end
if options[:ajax]
xhtml.div(:id => 'dynamic_recaptcha') {}
xhtml.script(:type => "text/javascript", :src => "#{uri}/js/recaptcha_ajax.js") {}
xhtml.script(:type => "text/javascript") do
xhtml.text! "Recaptcha.create('#{key}', document.getElementById('dynamic_recaptcha') );"
end
else
xhtml.script(:type => "text/javascript", :src => "#{uri}/challenge?k=#{key}&error=#{error}") {}
unless options[:noscript] == false
xhtml.noscript do
xhtml.iframe(:src => "#{uri}/noscript?k=#{key}",
:height => options[:iframe_height] ||= 300,
:width => options[:iframe_width] ||= 500,
:frameborder => 0) {}; xhtml.br
xhtml.textarea(:name => "recaptcha_challenge_field", :rows => 3, :cols => 40) {}
xhtml.input :name => "recaptcha_response_field",
:type => "hidden", :value => "manual_challenge"
end
end
end
raise ReCaptchaError, "No public key specified." unless key
return out
end # recaptcha_tags
end # Helpers
module Controller
# Your private API key must be specified in the environment variable +RECAPTCHA_PRIVATE_KEY+
def verify_recaptcha(model = nil)
return true if SKIP_VERIFY_ENV.include? ENV['RAILS_ENV']
raise ReCaptchaError, "No private key specified." unless ENV['RECAPTCHA_PRIVATE_KEY']
begin
recaptcha = Net::HTTP.post_form URI.parse("http://#{RECAPTCHA_VERIFY_SERVER}/verify"), {
:privatekey => ENV['RECAPTCHA_PRIVATE_KEY'],
:remoteip => request.remote_ip,
:challenge => params[:recaptcha_challenge_field],
:response => params[:recaptcha_response_field]
}
answer, error = recaptcha.body.split.map { |s| s.chomp }
unless answer == 'true'
session[:recaptcha_error] = error
model.valid? if model
model.errors.add_to_base "Captcha response is incorrect, please try again." if model
return false
else
session[:recaptcha_error] = nil
return true
end
rescue Exception => e
raise ReCaptchaError, e
end
end # verify_recaptcha
end # ControllerHelpers
class ReCaptchaError < StandardError; end
end # ReCaptcha
end # Ambethia

822
merb.thor Normal file
View File

@ -0,0 +1,822 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'rubygems/dependency_installer'
require 'rubygems/uninstaller'
require 'thor'
require 'fileutils'
require 'yaml'
module MerbThorHelper
private
# The current working directory, or Merb app root (--merb-root option).
def working_dir
@_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd)
end
# We should have a ./src dir for local and system-wide management.
def source_dir
@_source_dir ||= File.join(working_dir, 'src')
create_if_missing(@_source_dir)
@_source_dir
end
# If a local ./gems dir is found, it means we are in a Merb app.
def application?
gem_dir
end
# If a local ./gems dir is found, return it.
def gem_dir
if File.directory?(dir = File.join(working_dir, 'gems'))
dir
end
end
# If we're in a Merb app, we can have a ./bin directory;
# create it if it's not there.
def bin_dir
@_bin_dir ||= begin
if gem_dir
dir = File.join(working_dir, 'bin')
create_if_missing(dir)
dir
end
end
end
# Helper to create dir unless it exists.
def create_if_missing(path)
FileUtils.mkdir(path) unless File.exists?(path)
end
# Create a modified executable wrapper in the app's ./bin directory.
def ensure_local_bin_for(*gems)
if bin_dir && File.directory?(bin_dir)
gems.each do |gem|
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
spec = Gem::Specification.load(gemspec_path)
spec.executables.each do |exec|
if File.exists?(executable = File.join(gem_dir, 'bin', exec))
local_executable = File.join(bin_dir, exec)
puts "Adding local executable #{local_executable}"
File.open(local_executable, 'w', 0755) do |f|
f.write(executable_wrapper(spec, exec))
end
end
end
end
end
end
end
def executable_wrapper(spec, bin_file_name)
<<-TEXT
#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}
#
# This file was generated by merb.thor.
#
# The application '#{spec.name}' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
$BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
end
version = "#{Gem::Requirement.default}"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem '#{spec.name}', version
load '#{bin_file_name}'
TEXT
end
end
# TODO
# - a task to figure out an app's dependencies
# - pulling a specific UUID/Tag (gitspec hash) with clone/update
# - a 'deploy' task (in addition to 'redeploy' ?)
# - eventually take a --orm option for the 'merb-stack' type of tasks
class Merb < Thor
class SourcePathMissing < Exception
end
class GemPathMissing < Exception
end
class GemInstallError < Exception
end
class GemUninstallError < Exception
end
# Install a Merb stack from stable RubyForge gems. Optionally install a
# suitable Rack adapter/server when setting --adapter to one of the
# following: mongrel, emongrel, thin or ebb.
desc 'stable', 'Install extlib, merb-core and merb-more from rubygems'
method_options "--merb-root" => :optional,
"--adapter" => :optional
def stable
adapters = %w[mongrel emongrel thin ebb]
stable = Stable.new
stable.options = options
if stable.core && stable.more
puts "Installed extlib, merb-core and merb-more"
if options[:adapter] && adapters.include?(options[:adapter]) &&
stable.refresh_from_gems(options[:adapter])
puts "Installed #{options[:adapter]}"
elsif options[:adapter]
puts "Please specify one of the following adapters: #{adapters.join(' ')}"
end
end
end
class Stable < Thor
# The Stable tasks deal with known -stable- gems; available
# as shortcuts to Merb and DataMapper gems.
#
# These are pulled from rubyforge and installed into into the
# desired gems dir (either system-wide or into the application's
# gems directory).
include MerbThorHelper
# Gets latest gem versions from RubyForge and installs them.
#
# Examples:
#
# thor merb:edge:core
# thor merb:edge:core --merb-root ./path/to/your/app
# thor merb:edge:core --sources ./path/to/sources.yml
desc 'core', 'Install extlib and merb-core from git HEAD'
method_options "--merb-root" => :optional
def core
refresh_from_gems 'extlib', 'merb-core'
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
end
desc 'more', 'Install merb-more from rubygems'
method_options "--merb-root" => :optional
def more
refresh_from_gems 'merb-more'
ensure_local_bin_for('merb-gen')
end
desc 'plugins', 'Install merb-plugins from rubygems'
method_options "--merb-root" => :optional
def plugins
refresh_from_gems 'merb-plugins'
end
desc 'dm_core', 'Install dm-core from rubygems'
method_options "--merb-root" => :optional
def dm_core
refresh_from_gems 'extlib', 'dm-core'
end
desc 'dm_more', 'Install dm-more from rubygems'
method_options "--merb-root" => :optional
def dm_more
refresh_from_gems 'extlib', 'dm-core', 'dm-more'
end
# Pull from RubyForge and install.
def refresh_from_gems(*components)
gems = Gems.new
gems.options = options
components.all? { |name| gems.install(name) }
end
end
# Retrieve latest Merb versions from git and optionally install them.
#
# Note: the --sources option takes a path to a YAML file
# with a regular Hash mapping gem names to git urls.
#
# Examples:
#
# thor merb:edge
# thor merb:edge --install
# thor merb:edge --merb-root ./path/to/your/app
# thor merb:edge --sources ./path/to/sources.yml
desc 'edge', 'Install extlib, merb-core and merb-more from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def edge
edge = Edge.new
edge.options = options
edge.core
edge.more
end
class Edge < Thor
# The Edge tasks deal with known gems from the bleeding edge; available
# as shortcuts to Merb and DataMapper gems.
#
# These are pulled from git and optionally installed into into the
# desired gems dir (either system-wide or into the application's
# gems directory).
include MerbThorHelper
# Gets latest gem versions from git - optionally installs them.
#
# Note: the --sources option takes a path to a YAML file
# with a regular Hash mapping gem names to git urls,
# allowing pulling forks of the official repositories.
#
# Examples:
#
# thor merb:edge:core
# thor merb:edge:core --install
# thor merb:edge:core --merb-root ./path/to/your/app
# thor merb:edge:core --sources ./path/to/sources.yml
desc 'core', 'Update extlib and merb-core from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def core
refresh_from_source 'thor', 'extlib', 'merb-core'
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
end
desc 'more', 'Update merb-more from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def more
refresh_from_source 'merb-more'
ensure_local_bin_for('merb-gen')
end
desc 'plugins', 'Update merb-plugins from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def plugins
refresh_from_source 'merb-plugins'
end
desc 'dm_core', 'Update dm-core from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def dm_core
refresh_from_source 'extlib', 'dm-core'
end
desc 'dm_more', 'Update dm-more from git HEAD'
method_options "--merb-root" => :optional,
"--sources" => :optional,
"--install" => :boolean
def dm_more
refresh_from_source 'extlib', 'dm-core', 'dm-more'
end
private
# Pull from git and optionally install the resulting gems.
def refresh_from_source(*components)
source = Source.new
source.options = options
components.each do |name|
source.clone(name)
source.install(name) if options[:install]
end
end
end
class Source < Thor
# The Source tasks deal with gem source packages - mainly from github.
# Any directory inside ./src is regarded as a gem that can be packaged
# and installed from there into the desired gems dir (either system-wide
# or into the application's gems directory).
include MerbThorHelper
# Install a particular gem from source.
#
# If a local ./gems dir is found, or --merb-root is given
# the gems will be installed locally into that directory.
#
# Note that this task doesn't retrieve any (new) source from git;
# To update and install you'd execute the following two tasks:
#
# thor merb:source:update merb-core
# thor merb:source:install merb-core
#
# Alternatively, look at merb:edge and merb:edge:* with --install.
#
# Examples:
#
# thor merb:source:install merb-core
# thor merb:source:install merb-more
# thor merb:source:install merb-more/merb-slices
# thor merb:source:install merb-plugins/merb_helpers
# thor merb:source:install merb-core --merb-root ./path/to/your/app
desc 'install GEM_NAME', 'Install a rubygem from (git) source'
method_options "--merb-root" => :optional
def install(name)
puts "Installing #{name}..."
gem_src_dir = File.join(source_dir, name)
opts = {}
opts[:install_dir] = gem_dir if gem_dir
Merb.install_gem_from_src(gem_src_dir, opts)
rescue Merb::SourcePathMissing
puts "Missing rubygem source path: #{gem_src_dir}"
rescue Merb::GemPathMissing
puts "Missing rubygems path: #{gem_dir}"
rescue => e
puts "Failed to install #{name} (#{e.message})"
end
# Clone a git repository into ./src. The repository can be
# a direct git url or a known -named- repository.
#
# Examples:
#
# thor merb:source:clone dm-core
# thor merb:source:clone dm-core --sources ./path/to/sources.yml
# thor merb:source:clone git://github.com/sam/dm-core.git
desc 'clone REPOSITORY', 'Clone a git repository into ./src'
method_options "--sources" => :optional
def clone(repository)
if repository =~ /^git:\/\//
repository_url = repository
elsif url = Merb.repos(options[:sources])[repository]
repository_url = url
end
if repository_url
repository_name = repository_url[/([\w+|-]+)\.git/u, 1]
fork_name = repository_url[/.com\/+?(.+)\/.+\.git/u, 1]
local_repo_path = "#{source_dir}/#{repository_name}"
if File.directory?(local_repo_path)
puts "\n#{repository_name} repository exists, updating or branching instead of cloning..."
FileUtils.cd(local_repo_path) do
# to avoid conflicts we need to set a remote branch for non official repos
existing_repos = `git remote -v`.split("\n").map{|branch| branch.split(/\s+/)}
origin_repo_url = existing_repos.detect{ |r| r.first == "origin" }.last
# pull from the original repository - no branching needed
if repository_url == origin_repo_url
puts "Pulling from #{repository_url}"
system %{
git fetch
git checkout master
git rebase origin/master
}
# update and switch to a branch for a particular github fork
elsif existing_repos.map{ |r| r.last }.include?(repository_url)
puts "Switching to remote branch: #{fork_name}"
`git checkout -b #{fork_name} #{fork_name}/master`
`git rebase #{fork_name}/master`
# create a new remote branch for a particular github fork
else
puts "Add a new remote branch: #{fork_name}"
`git remote add -f #{fork_name} #{repository_url}`
`git checkout -b#{fork_name} #{fork_name}/master`
end
end
else
FileUtils.cd(source_dir) do
puts "\nCloning #{repository_name} repository from #{repository_url}..."
system("git clone --depth=1 #{repository_url} ")
end
end
else
puts "No valid repository url given"
end
end
# Update a specific gem source directory from git. See #clone.
desc 'update REPOSITORY_URL', 'Update a git repository in ./src'
alias :update :clone
# Update all gem sources from git - based on the current branch.
desc 'refresh', 'Pull fresh copies of all source gems'
def refresh
repos = Dir["#{source_dir}/*"]
repos.each do |repo|
next unless File.directory?(repo) && File.exists?(File.join(repo, '.git'))
FileUtils.cd(repo) do
puts "Refreshing #{File.basename(repo)}"
branch = `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1) /'`[/\* (.+)/, 1]
system %{git rebase #{branch}}
end
end
end
end
class Gems < Thor
# The Gems tasks deal directly with rubygems, either through remotely
# available sources (rubyforge for example) or by searching the
# system-wide gem cache for matching gems. The gems are installed from
# there into the desired gems dir (either system-wide or into the
# application's gems directory).
include MerbThorHelper
# Install a gem and its dependencies.
#
# If a local ./gems dir is found, or --merb-root is given
# the gems will be installed locally into that directory.
#
# The option --cache will look in the system's gem cache
# for the latest version and install it in the apps' gems.
# This is particularly handy for gems that aren't available
# through rubyforge.org - like in-house merb slices etc.
#
# Examples:
#
# thor merb:gems:install merb-core
# thor merb:gems:install merb-core --cache
# thor merb:gems:install merb-core --version 0.9.7
# thor merb:gems:install merb-core --merb-root ./path/to/your/app
desc 'install GEM_NAME', 'Install a gem from rubygems'
method_options "--version" => :optional,
"--merb-root" => :optional,
"--cache" => :boolean
def install(name)
puts "Installing #{name}..."
opts = {}
opts[:version] = options[:version]
opts[:cache] = options[:cache] if gem_dir
opts[:install_dir] = gem_dir if gem_dir
Merb.install_gem(name, opts)
rescue => e
puts "Failed to install #{name} (#{e.message})"
end
# Update a gem and its dependencies.
#
# If a local ./gems dir is found, or --merb-root is given
# the gems will be installed locally into that directory.
#
# The option --cache will look in the system's gem cache
# for the latest version and install it in the apps' gems.
# This is particularly handy for gems that aren't available
# through rubyforge.org - like in-house merb slices etc.
#
# Examples:
#
# thor merb:gems:update merb-core
# thor merb:gems:update merb-core --cache
# thor merb:gems:update merb-core --merb-root ./path/to/your/app
desc 'update GEM_NAME', 'Update a gem from rubygems'
method_options "--merb-root" => :optional,
"--cache" => :boolean
def update(name)
puts "Updating #{name}..."
opts = {}
if gem_dir
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{name}-*.gemspec")].last
gemspec = Gem::Specification.load(gemspec_path)
opts[:version] = Gem::Requirement.new [">#{gemspec.version}"]
end
opts[:install_dir] = gem_dir
opts[:cache] = options[:cache]
end
Merb.install_gem(name, opts)
rescue => e
puts "Failed to update #{name} (#{e.message})"
end
# Uninstall a gem - ignores dependencies.
#
# If a local ./gems dir is found, or --merb-root is given
# the gems will be uninstalled locally from that directory.
#
# Examples:
#
# thor merb:gems:uninstall merb-core
# thor merb:gems:uninstall merb-core --all
# thor merb:gems:uninstall merb-core --version 0.9.7
# thor merb:gems:uninstall merb-core --merb-root ./path/to/your/app
desc 'install GEM_NAME', 'Install a gem from rubygems'
desc 'uninstall GEM_NAME', 'Uninstall a gem'
method_options "--version" => :optional,
"--merb-root" => :optional,
"--all" => :boolean
def uninstall(name)
puts "Uninstalling #{name}..."
opts = {}
opts[:ignore] = true
opts[:all] = options[:all]
opts[:executables] = true
opts[:version] = options[:version]
opts[:install_dir] = gem_dir if gem_dir
Merb.uninstall_gem(name, opts)
rescue => e
puts "Failed to uninstall #{name} (#{e.message})"
end
# Completely remove a gem and all its versions - ignores dependencies.
#
# If a local ./gems dir is found, or --merb-root is given
# the gems will be uninstalled locally from that directory.
#
# Examples:
#
# thor merb:gems:wipe merb-core
# thor merb:gems:wipe merb-core --merb-root ./path/to/your/app
desc 'wipe GEM_NAME', 'Remove a gem completely'
method_options "--merb-root" => :optional
def wipe(name)
puts "Wiping #{name}..."
opts = {}
opts[:ignore] = true
opts[:all] = true
opts[:executables] = true
opts[:install_dir] = gem_dir if gem_dir
Merb.uninstall_gem(name, opts)
rescue => e
puts "Failed to wipe #{name} (#{e.message})"
end
# This task should be executed as part of a deployment setup, where
# the deployment system runs this after the app has been installed.
# Usually triggered by Capistrano, God...
#
# It will regenerate gems from the bundled gems cache for any gem
# that has C extensions - which need to be recompiled for the target
# deployment platform.
desc 'redeploy', 'Recreate any binary gems on the target deployment platform'
def redeploy
require 'tempfile' # for
if File.directory?(specs_dir = File.join(gem_dir, 'specifications')) &&
File.directory?(cache_dir = File.join(gem_dir, 'cache'))
Dir[File.join(specs_dir, '*.gemspec')].each do |gemspec_path|
unless (gemspec = Gem::Specification.load(gemspec_path)).extensions.empty?
if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
# Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
# will complain about copying identical files (same source/destination).
FileUtils.cp(gem_file, gem_file_copy)
Merb.install_gem(gem_file_copy, :install_dir => gem_dir)
File.delete(gem_file_copy)
end
end
end
else
puts "No application local gems directory found"
end
end
end
class << self
# Default Git repositories - pass source_config option
# to load a yaml configuration file.
def repos(source_config = nil)
@_repos ||= begin
repositories = {
'merb-core' => "git://github.com/wycats/merb-core.git",
'merb-more' => "git://github.com/wycats/merb-more.git",
'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
'extlib' => "git://github.com/sam/extlib.git",
'dm-core' => "git://github.com/sam/dm-core.git",
'dm-more' => "git://github.com/sam/dm-more.git",
'thor' => "git://github.com/wycats/thor.git"
}
end
if source_config && File.exists?(source_config)
@_repos.merge(YAML.load(File.read(source_config)))
else
@_repos
end
end
# Install a gem - looks remotely and local gem cache;
# won't process rdoc or ri options.
def install_gem(gem, options = {})
from_cache = (options.key?(:cache) && options.delete(:cache))
if from_cache
install_gem_from_cache(gem, options)
else
version = options.delete(:version)
Gem.configuration.update_sources = false
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
exception = nil
begin
installer.install gem, version
rescue Gem::InstallError => e
exception = e
rescue Gem::GemNotFoundException => e
if from_cache && gem_file = find_gem_in_cache(gem, version)
puts "Located #{gem} in gem cache..."
installer.install gem_file
else
exception = e
end
rescue => e
exception = e
end
if installer.installed_gems.empty? && exception
puts "Failed to install gem '#{gem}' (#{exception.message})"
end
installer.installed_gems.each do |spec|
puts "Successfully installed #{spec.full_name}"
end
end
end
# Install a gem - looks in the system's gem cache instead of remotely;
# won't process rdoc or ri options.
def install_gem_from_cache(gem, options = {})
version = options.delete(:version)
Gem.configuration.update_sources = false
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
exception = nil
begin
if gem_file = find_gem_in_cache(gem, version)
puts "Located #{gem} in gem cache..."
installer.install gem_file
else
raise Gem::InstallError, "Unknown gem #{gem}"
end
rescue Gem::InstallError => e
exception = e
end
if installer.installed_gems.empty? && exception
puts "Failed to install gem '#{gem}' (#{e.message})"
end
installer.installed_gems.each do |spec|
puts "Successfully installed #{spec.full_name}"
end
end
# Install a gem from source - builds and packages it first then installs it.
def install_gem_from_src(gem_src_dir, options = {})
raise SourcePathMissing unless File.directory?(gem_src_dir)
raise GemPathMissing if options[:install_dir] && !File.directory?(options[:install_dir])
gem_name = File.basename(gem_src_dir)
gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
# We need to use local bin executables if available.
thor = which('thor')
rake = which('rake')
# Handle pure Thor installation instead of Rake
if File.exists?(File.join(gem_src_dir, 'Thorfile'))
# Remove any existing packages.
FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
# Create the package.
FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
# Install the package using rubygems.
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
FileUtils.cd(File.dirname(package)) do
install_gem(File.basename(package), options.dup)
return
end
else
raise Merb::GemInstallError, "No package found for #{gem_name}"
end
# Handle standard installation through Rake
else
# Clean and regenerate any subgems for meta gems.
Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
FileUtils.cd(File.dirname(rakefile)) { system("#{rake} clobber_package; #{rake} package") }
end
# Handle the main gem install.
if File.exists?(File.join(gem_src_dir, 'Rakefile'))
# Remove any existing packages.
FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
# Create the main gem pkg dir if it doesn't exist.
FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
# Copy any subgems to the main gem pkg dir.
Dir[File.join(gem_src_dir, '**', 'pkg', '*.gem')].each do |subgem_pkg|
FileUtils.cp(subgem_pkg, gem_pkg_dir)
end
# Finally generate the main package and install it; subgems
# (dependencies) are local to the main package.
FileUtils.cd(gem_src_dir) do
system("#{rake} package")
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
FileUtils.cd(File.dirname(package)) do
install_gem(File.basename(package), options.dup)
return
end
else
raise Merb::GemInstallError, "No package found for #{gem_name}"
end
end
end
end
raise Merb::GemInstallError, "No Rakefile found for #{gem_name}"
end
# Uninstall a gem.
def uninstall_gem(gem, options = {})
if options[:version] && !options[:version].is_a?(Gem::Requirement)
options[:version] = Gem::Requirement.new ["= #{version}"]
end
begin
Gem::Uninstaller.new(gem, options).uninstall
rescue => e
raise GemUninstallError, "Failed to uninstall #{gem}"
end
end
# Will prepend sudo on a suitable platform.
def sudo
@_sudo ||= begin
windows = PLATFORM =~ /win32|cygwin/ rescue nil
windows ? "" : "sudo "
end
end
# Use the local bin/* executables if available.
def which(executable)
if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
exec
else
executable
end
end
private
def find_gem_in_cache(gem, version)
spec = if version
version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
Gem.source_index.find_name(gem, version).first
else
Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
end
if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
gem_file
end
end
end
class Tasks < Thor
include MerbThorHelper
# Install Thor, Rake and RSpec into the local gems dir, by copying it from
# the system-wide rubygems cache - which is OK since we needed it to run
# this task already.
#
# After this we don't need the system-wide rubygems anymore, as all required
# executables are available in the local ./bin directory.
#
# RSpec is needed here because source installs might fail when running
# rake tasks where spec/rake/spectask has been required.
desc 'setup', 'Install Thor, Rake and RSpec in the local gems dir'
method_options "--merb-root" => :optional
def setup
if $0 =~ /^(\.\/)?bin\/thor$/
puts "You cannot run the setup from #{$0} - try #{File.basename($0)} merb:tasks:setup instead"
return
end
create_if_missing(File.join(working_dir, 'gems'))
Merb.install_gem('thor', :cache => true, :install_dir => gem_dir)
Merb.install_gem('rake', :cache => true, :install_dir => gem_dir)
Merb.install_gem('rspec', :cache => true, :install_dir => gem_dir)
ensure_local_bin_for('thor', 'rake', 'rspec')
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/images/footer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

BIN
public/images/merb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

BIN
public/images/vote.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

View File

@ -0,0 +1,28 @@
function addLoadEvent(func)
{
var oldonload = window.onload;
if (typeof window.onload != 'function')
{
window.onload = func;
}
else
{
window.onload = function()
{
oldonload();
func();
}
}
}
function hide_flashes()
{
addLoadEvent(function(){
setTimeout('hide_flashes_timer()', 30000);
});
}
function hide_flashes_timer()
{
new Effect.DropOut($('flash_container'));
}

974
public/javascripts/dragdrop.js vendored Normal file
View File

@ -0,0 +1,974 @@
// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(Object.isUndefined(Effect))
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || { });
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if(Object.isArray(containment)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var drop, affected = [];
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0)
drop = Droppables.findDeepestChild(affected);
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop != this.last_active) Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop) {
this.last_active.onDrop(element, this.last_active.element, event);
return true;
}
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
}
/*--------------------------------------------------------------------------*/
var Draggable = Class.create({
initialize: function(element) {
var defaults = {
handle: false,
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
},
endeffect: function(element) {
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
},
zindex: 1000,
revert: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || { });
this.element = $(element);
if(options.handle && Object.isString(options.handle))
this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if((tag_name = src.tagName.toUpperCase()) && (
tag_name=='INPUT' ||
tag_name=='SELECT' ||
tag_name=='OPTION' ||
tag_name=='BUTTON' ||
tag_name=='TEXTAREA')) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(!this.delta)
this.delta = this.currentDelta();
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
if (!this.element._originallyAbsolute)
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
if(!this.options.quiet){
Position.prepare();
Droppables.show(pointer, this.element);
}
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.quiet){
Position.prepare();
var pointer = [Event.pointerX(event), Event.pointerY(event)];
Droppables.show(pointer, this.element);
}
if(this.options.ghosting) {
if (!this.element._originallyAbsolute)
Position.relativize(this.element);
delete this.element._originallyAbsolute;
Element.remove(this._clone);
this._clone = null;
}
var dropped = false;
if(success) {
dropped = Droppables.fire(event, this.element);
if (!dropped) dropped = false;
}
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && Object.isFunction(revert)) revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
if (dropped == 0 || revert != 'failure')
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = Position.cumulativeOffset(this.element);
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(Object.isFunction(this.options.snap)) {
p = this.options.snap(p[0],p[1],this);
} else {
if(Object.isArray(this.options.snap)) {
p = p.map( function(v, i) {
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
} else {
p = p.map( function(v) {
return (v/this.options.snap).round()*this.options.snap }.bind(this))
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return { top: T, left: L, width: W, height: H };
}
});
Draggable._dragging = { };
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create({
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
});
var Sortable = {
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
sortables: { },
_findRootElement: function(element) {
while (element.tagName.toUpperCase() != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
var s = Sortable.options(element);
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
delay: 0,
hoverclass: null,
ghosting: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
// these take arrays of elements or ids and can be
// used for better initialization performance
elements: false,
handles: false,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || { });
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
quiet: options.quiet,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
}
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
}
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
var handle = options.handles ? $(options.handles[i]) :
(options.handle ? $(e).select('.' + options.handle)[0] : e);
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.id] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Sortable._marker.hide();
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker =
($('dropmarker') || Element.extend(document.createElement('DIV'))).
hide().addClassName('dropmarker').setStyle({position:'absolute'});
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Sortable._marker.show();
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
}
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child)
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || { });
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
}
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || { });
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || { });
var nodeMap = { };
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || { });
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
}
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
}
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
}
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

1122
public/javascripts/effects.js vendored Normal file

File diff suppressed because it is too large Load Diff

4221
public/javascripts/prototype.js vendored Normal file

File diff suppressed because it is too large Load Diff

121
public/stylesheets/ba.css Normal file
View File

@ -0,0 +1,121 @@
body {
margin: 0px;
padding: 0px;
background-color: #e8e8e8;
text-align: center;
font: 14px Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
}
a {
text-decoration: none;
}
a img {
border: none;
vertical-align: bottom;
text-decoration: none;
}
a, a:visited, a:hover {
color: #c10808;
}
fieldset {
width: 500px;
margin-left: 25px;
}
label {
margin-right: 5px;
}
#container {
margin: 0 auto;
width: 750px;
position: relative;
background: #ffffff;
padding: 0px;
text-align: left;
}
#flash_container {
padding: 5px 0px;
}
#flash_container div {
padding: 5px;
font-weight: bold;
}
.notice {
background-color: #edd400;
border-top: 1px solid #c4a000;
border-bottom: 1px solid #c4a000;
}
.error {
background-color: #ff6767;
border-top: 1px solid #a40000;
border-bottom: 1px solid #a40000;
}
#header {
height: 120px;
padding-top: 10px;
}
#header_image {
margin-left: 30px;
}
#tool_bar {
margin-top: 7px;
padding: 3px 10px 3px 10px;
border-top: 1px solid #c17d11;
border-bottom: 1px solid #c17d11;
background-color: #e9b96e;
}
#tool_bar a {
font-weight: bold;
font-size: 1.1em;
padding-left: 3px;
padding-right: 3px;
text-decoration: none;
color: #8f5902;
}
#tool_bar a:hover {
color: #5e3a01;
}
#content {
margin: 5px;
}
#content a:hover {
color: #204a87;
}
#footer {
font-size: 12px;
height: 25px;
width: 90%;
border-top: 2px solid #e8e8e8;
text-align: right;
padding: 5px 0 10px 0;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}
#footer a:hover {
text-decoration: underline;
}
.no_list_style {
list-style-type: none;
}
#recaptcha_container {
margin: 10px 0px 10px 0px;

View File

@ -0,0 +1,119 @@
body {
font-family: Arial, Verdana, sans-serif;
font-size: 12px;
background-color: #fff;
}
* {
margin: 0px;
padding: 0px;
text-decoration: none;
}
html {
height: 100%;
margin-bottom: 1px;
}
#container {
width: 80%;
text-align: left;
background-color: #fff;
margin-right: auto;
margin-left: auto;
}
#header-container {
width: 100%;
padding-top: 15px;
}
#header-container h1, #header-container h2 {
margin-left: 6px;
margin-bottom: 6px;
}
.spacer {
width: 100%;
height: 15px;
}
hr {
border: 0px;
color: #ccc;
background-color: #cdcdcd;
height: 1px;
width: 100%;
text-align: left;
}
h1 {
font-size: 28px;
color: #c55;
background-color: #fff;
font-family: Arial, Verdana, sans-serif;
font-weight: 300;
}
h2 {
font-size: 15px;
color: #999;
font-family: Arial, Verdana, sans-serif;
font-weight: 300;
background-color: #fff;
}
h3 {
color: #4d9b12;
font-size: 15px;
text-align: left;
font-weight: 300;
padding: 5px;
margin-top: 5px;
}
#left-container {
float: left;
width: 250px;
background-color: #FFFFFF;
color: black;
}
#left-container h3 {
color: #c55;
}
#main-container {
margin: 5px 5px 5px 260px;
padding: 15px;
border-left: 1px solid silver;
min-height: 400px;
}
p {
color: #000;
background-color: #fff;
line-height: 20px;
padding: 5px;
}
a {
color: #4d9b12;
background-color: #fff;
text-decoration: none;
}
a:hover {
color: #4d9b12;
background-color: #fff;
text-decoration: underline;
}
#footer-container {
clear: both;
font-size: 12px;
font-family: Verdana, Arial, sans-serif;
}
.right {
float: right;
font-size: 100%;
margin-top: 5px;
color: #999;
background-color: #fff;
}
.left {
float: left;
font-size: 100%;
margin-top: 5px;
color: #999;
background-color: #fff;
}
#main-container ul {
margin-left: 3.0em;
}

View File

@ -0,0 +1,15 @@
class CreateUsersMigration < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :user_name, :limit => 32
t.string :auth_token
t.boolean :administrator
t.datetime :created_at
end
add_index :users, :user_name
end
def self.down
drop_table :users
end
end

View File

@ -0,0 +1,16 @@
class CreatePhotosMigration < ActiveRecord::Migration
def self.up
create_table :photos do |t|
t.string :filename
t.integer :user_id
t.string :owner_token
t.datetime :created_at
end
add_index :photos, :user_id
add_index :photos, :owner_token
end
def self.down
drop_table :photos
end
end

View File

@ -0,0 +1,14 @@
class CreateVotesMigration < ActiveRecord::Migration
def self.up
create_table :votes do |t|
t.integer :photo_id, :user_id
t.boolean :vote
end
add_index :votes, :photo_id
add_index :votes, :user_id
end
def self.down
drop_table :votes
end
end

View File

@ -0,0 +1,14 @@
class CreateSessionsMigration < ActiveRecord::Migration
def self.up
create_table :sessions do |t|
t.column :session_id, :string
t.column :data, :text
t.column :updated_at, :datetime
end
add_index :sessions, :session_id
end
def self.down
drop_table :sessions
end
end

50
schema/schema.rb Normal file
View File

@ -0,0 +1,50 @@
# This file is auto-generated from the current state of the database. Instead of editing this file,
# please use the migrations feature of Active Record to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 4) do
create_table "photos", :force => true do |t|
t.string "filename"
t.integer "user_id"
t.string "owner_token"
t.datetime "created_at"
end
add_index "photos", ["owner_token"], :name => "index_photos_on_owner_token"
add_index "photos", ["user_id"], :name => "index_photos_on_user_id"
create_table "sessions", :force => true do |t|
t.string "session_id"
t.text "data"
t.datetime "updated_at"
end
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
create_table "users", :force => true do |t|
t.string "user_name", :limit => 32
t.string "auth_token"
t.boolean "administrator"
t.datetime "created_at"
end
add_index "users", ["user_name"], :name => "index_users_on_user_name"
create_table "votes", :force => true do |t|
t.integer "photo_id"
t.integer "user_id"
t.boolean "vote"
end
add_index "votes", ["user_id"], :name => "index_votes_on_user_id"
add_index "votes", ["photo_id"], :name => "index_votes_on_photo_id"
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Favorites, "index action" do
before(:each) do
dispatch_to(Favorites, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Home, "index action" do
before(:each) do
dispatch_to(Home, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Photos, "index action" do
before(:each) do
dispatch_to(Photos, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Sessions, "index action" do
before(:each) do
dispatch_to(Sessions, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Stats, "index action" do
before(:each) do
dispatch_to(Stats, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Users, "index action" do
before(:each) do
dispatch_to(Users, :index)
end
end

View File

@ -0,0 +1,7 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Votes, "index action" do
before(:each) do
dispatch_to(Votes, :index)
end
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::FavoritesHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::HomeHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::PhotosHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::SessionsHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::StatsHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::UsersHelper do
end

View File

@ -0,0 +1,5 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
describe Merb::VotesHelper do
end

View File

@ -0,0 +1,7 @@
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
describe Photo do
it "should have specs"
end

7
spec/models/user_spec.rb Normal file
View File

@ -0,0 +1,7 @@
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
describe User do
it "should have specs"
end

7
spec/models/vote_spec.rb Normal file
View File

@ -0,0 +1,7 @@
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
describe Vote do
it "should have specs"
end

0
spec/spec.opts Normal file
View File

13
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,13 @@
require 'rubygems'
require 'merb-core'
require 'spec' # Satisfies Autotest and anyone else not using the Rake tasks
# this loads all plugins required in your init file so don't add them
# here again, Merb will do it for you
Merb.start_environment(:testing => true, :adapter => 'runner', :environment => ENV['MERB_ENV'] || 'test')
Spec::Runner.configure do |config|
config.include(Merb::Test::ViewHelper)
config.include(Merb::Test::RouteHelper)
config.include(Merb::Test::ControllerHelper)
end

14
test/test_helper.rb Normal file
View File

@ -0,0 +1,14 @@
$TESTING=true
require 'rubygems'
require 'merb-core'
# TODO: Boot Merb, via the Test Rack adapter
Merb.start :environment => (ENV['MERB_ENV'] || 'test'),
:merb_root => File.join(File.dirname(__FILE__), ".." )
class Test::Unit::TestCase
include Merb::Test::RequestHelper
# Add more helper methods to be used by all tests here...
end