adding taggable images

git-svn-id: http://svn.barleysodas.com/barleysodas/trunk@103 0f7b21a7-9e3a-4941-bbeb-ce5c7c368fa7
master
andrew 2008-02-01 04:46:06 +00:00
parent 543d65ebb6
commit 044824947b
47 changed files with 986 additions and 84 deletions

View File

@ -0,0 +1,78 @@
class GalleriesController < ApplicationController
append_before_filter :fetch_image, :only => [ :show, :destroy,
:download_original ]
# GET /images
# GET /images.xml
def index
@content_title = 'Image Gallery'
cond_ary = []
cond_var = {
:people_id => params[:id]
}
@secondary_title = "Everybody's Images"
if params[:id]
cond_ary << 'images.people_id = :people_id'
@people = People.find(params[:id])
@secondary_title = "Images from #{@people.title}"
end
cond_ary << '1 = 1' if cond_ary.empty?
@pages, @images = paginate :images, :per_page => per_page,
:order => 'images.created_at DESC', :include => [ 'people' ],
:conditions => [ cond_ary.join(' AND '), cond_var ]
flash.now[:notice] = 'There are no images yet.' if @images.empty?
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @images.to_xml }
end
end
# GET /galleries/1
# GET /galleries/1.xml
def show
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @image.to_xml }
end
end
# GET /galleries/new
def new
@image = Image.new
end
# POST /images
# POST /images.xml
def create
@image = Image.new(params[:image])
if @image.save
flash[:notice] = 'Great success!'
redirect_to gallery_url(@image)
else
render :action => :new
end
end
# DELETE /galleries/1
# DELETE /galleries/1.xml
def destroy
@image.destroy
flash[:notice] = 'Destroyed the image.'
redirect_to galleries_url(:id => @image.people_id)
end
##
# Sends a copy of the original Image to the People.
#
def download_original
send_file("#{RAILS_ROOT}/public/images/" +
@image.filename_for_version(:original),
:disposition => 'inline', :type => @image.content_type)
end
protected
def fetch_image
@image = Image.find(params[:id])
end
end

View File

@ -0,0 +1,88 @@
class TagImagesController < ApplicationController
# GET /tag_images
# GET /tag_images.xml
def index
redirect_to images_url
end
# GET /tag_images/1
# GET /tag_images/1.xml
def show
@content_title = 'Tag your friends and beers!'
@image = Image.find(params[:id], :include => [ :tag_images ])
@tag_images = @image.tag_images
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @tag_images.to_xml }
end
end
# POST /tag_images
# POST /tag_images.xml
def create
@tag_image = TagImage.new(params[:tag_image])
@image = @tag_image.image
if @tag_image.save
@tag_images = @image.tag_images
render :partial => 'tag_images'
else
render :partial => 'tag_image_errors', :status => 500
end
end
# DELETE /tag_images/1
# DELETE /tag_images/1.xml
def destroy
@tag_image = TagImage.find params[:id], :include => [ :image ]
@image = @tag_image.image
@tag_image.destroy
@image.tag_images.reload
@tag_images = @image.tag_images
render :partial => 'tag_images'
end
##
# Searches for all known models that support image tagging. Sticks all of
# the matching results into a hash that is indexed by the type.
#
def taggable_search
@results = {}
cond_ary = [ 'title ILIKE :title' ]
cond_var = { :title => "%#{params[:name]}%" }
TagImage.types_for_select.flatten.each do |ctype|
klass = Class.class_eval(ctype)
@results[ctype] = klass.find :all, :order => 'title ASC',
:conditions => [ cond_ary.join(' AND '), cond_var ]
end
render :partial => 'taggable_results'
end
##
# Renders an Ajax browser of all tagged Image models for any +:taggable_type+
#
def tagged_images
images_per_page = 4
@page_count, @current_page, @tagged_type, @tagged_images = nil, nil, nil, nil
@tagged_type = params[:tagged_type]
if TagImage.types_for_select.flatten.include?(@tagged_type)
cond_ary = [
'tagged_type = :tt',
'tagged_id = :tid'
]
cond_var = { :tt => @tagged_type, :tid => params[:id] }
conditions = [ cond_ary.join(' AND '), cond_var ]
@current_page = params[:page].to_i
@current_page = 1 if @current_page == 0
image_count = TagImage.count(conditions)
@page_count = (image_count.to_f / per_page.to_f + 0.5).to_i
@page_count = 1 if @page_count == 0 and image_count >= 0
@tagged_images = TagImage.find :all, :limit => images_per_page,
:conditions => conditions, :order => 'created_at ASC',
:offset => ((@current_page - 1) * images_per_page),
:include => [ 'image' ]
render :partial => 'tag_images/tagged_images'
else
render :nothing => true, :status => 500
end
end
end

View File

@ -91,4 +91,48 @@ module ApplicationHelper
res
end
end
##
# Captures a block output and renders it in a partial as <tt>body</tt>
#
def block_to_partial(partial_name, options = {}, &block)
options.merge!(:body => capture(&block))
concat(render(:partial => partial_name, :locals => options), block.binding)
end
##
# Helper to build a prototype dialog.
#
def lightbox(options = {}, &block)
options = {
:title => 'DialogTitle',
:window_id => 'DialogId',
:modal => false
}.merge(options)
block_to_partial('shared/lightbox', options, &block)
end
##
# Pagination link image browser thingey for the tagged image lightbox.
#
def image_browser_navigation_link(image_name, page_number, total_pages,
tagged_class, tagged_id)
if page_number == 0 or
(page_number == 1 and total_pages == 1) or
(page_number > total_pages)
image_tag(image_name)
else
link_to_remote image_tag(image_name), :update => 'browser_box',
:url => { :controller => 'tag_images', :action => 'tagged_images',
:id => :tagged_id, :tagged_class => tagged_class }
end
end
##
# Link to open the dialog box for the tagged image browser.
#
def tagged_image_browser_link
link_to_function 'Tagged Images',
"lightboxes['tagged_image_browser'].open()"
end
end

View File

@ -0,0 +1,5 @@
module GalleriesHelper
def new_image_link
link_to 'Upload Image', new_gallery_url
end
end

View File

@ -0,0 +1,2 @@
module TagImagesHelper
end

View File

@ -6,6 +6,7 @@ class Beer < ActiveRecord::Base
has_one_tuxwiki_page :owner_class => 'Beer'
belongs_to :style
validates_presence_of :style_id
has_many_tagged_images
##
# Returns a list of attributes for the Page partial.

96
app/models/image.rb Normal file
View File

@ -0,0 +1,96 @@
require 'mini_magick'
class Image < ActiveRecord::Base
attr_accessor :file
belongs_to :people
validates_presence_of :people_id
before_validation_on_create :set_people_id
before_create :validate_image_sanity
after_create :setup_directories
before_destroy :destroy_directories
has_many :tag_images, :dependent => :destroy
has_many :tagged_items, :through => :tag_images
##
# Builds the filename for this model for a particular version of the file.
#
def filename_for_version(ver = :screen)
if respond_to?(ver)
"community/#{id}/#{self.send(ver)}"
else
"/images/image-missing.png"
end
end
protected
##
# Determines the base directory for all files in this model.
#
def base_directory
"#{RAILS_ROOT}/public/images/community/#{id}"
end
##
# Sets the People marker for ownership on creation.
#
def set_people_id
self[:people_id] = ApplicationController.current_people_id rescue nil
self[:people_id] ||= People.penguincoder.id rescue nil
end
##
# Checks to make sure that the file exists and is an image.
#
def validate_image_sanity
if @file.nil? or @file.to_s.empty?
errors.add(:file, 'is not a file')
return false
end
errors.add(:file, 'is too big (3MB max)') if @file.size > 3 * 1048576
begin
@magick_image = MiniMagick::Image.from_blob(@file.read,
File.extname(@file.original_filename))
rescue
logger.debug("Caught an exception saving an image:")
logger.debug("* #{$!}")
errors.add(:file, 'is not an image')
end
return false if self.errors.size > 0
self.content_type = @file.content_type.chomp
true
end
##
# Makes the directories and writes the different versions for the uploaded
# files if applicable.
#
def setup_directories
Dir.mkdir(base_directory) unless File.exist?(base_directory)
self.original = File.basename(@file.original_filename).gsub(/[^\w._-]/, '')
@magick_image.write("#{base_directory}/#{self.original}")
@magick_image.thumbnail("600x600>")
self.screen = "screen_#{self.original}"
@magick_image.write("#{base_directory}/#{self.screen}")
if @magick_image.output =~ / (\d+)x(\d+) /
self.screen_width = $1
self.screen_height = $2
end
@magick_image.thumbnail("50x50>")
self.thumbnail = "thumbnail_#{self.original}"
@magick_image.write("#{base_directory}/#{self.thumbnail}")
self.save
end
##
# Removes the directories and files associated with this model on destroy.
#
def destroy_directories
return unless File.exists?(base_directory)
Dir.foreach(base_directory) do |file|
next if file =~ /^\.\.?$/
File.delete(base_directory + '/' + file)
end
Dir.delete(base_directory)
end
end

View File

@ -7,6 +7,8 @@ class People < ActiveRecord::Base
attr_protected :role_id
has_many :created_pages, :class_name => 'Page', :foreign_key => 'created_by'
has_many :updated_pages, :class_name => 'Page', :foreign_key => 'updated_by'
has_many :images, :dependent => :destroy
has_many_tagged_images
validates_uniqueness_of :title
make_authenticatable

10
app/models/tag_image.rb Normal file
View File

@ -0,0 +1,10 @@
class TagImage < ActiveRecord::Base
belongs_to :image
belongs_to :tagged, :polymorphic => true
validates_presence_of :image_id, :tagged_id, :tagged_type
validates_uniqueness_of :tagged_id, :scope => :tagged_type
def self.types_for_select
[ 'Beer', 'People', 'Brewery' ].collect { |x| [x] }
end
end

View File

@ -1,7 +1,10 @@
<%= render :partial => 'shared/tagged_image_browser', :locals => { :obj => @beer } %>
<%= render :partial => 'pages/page' %>
<% content_for :sidebar do -%>
<%= new_beer_link -%><br />
<%= edit_beer_link(@beer) -%><br />
<%= link_to 'Destroy', beer_path(@beer.page.title_for_url), :confirm => 'Are you sure?', :method => :delete %><br />
<% unless @beer.tagged_images.empty? -%><%= tagged_image_browser_link -%><br /><% end -%>
<% end -%>

View File

@ -4,4 +4,5 @@
<%= new_brewery_link -%><br />
<%= edit_brewery_link(@brewery) -%><br />
<%= link_to 'Destroy', brewery_path(@brewery.page.title_for_url), :confirm => 'Are you sure?', :method => :delete %><br />
<%= tagged_image_browser_link -%><br />
<% end -%>

View File

@ -0,0 +1,6 @@
<br />
<% version ||= :screen -%>
<div class="people_image" id="image_<%= image.id -%>">
<%= link_to_unless_current(image_tag(image.filename_for_version(version), :alt => image.original), gallery_url(image)) %>
<p class="author">Uploaded by <%= link_to(image.people.title, galleries_url(:id => image.people_id)) -%></p>
</div>

View File

@ -0,0 +1,9 @@
<fieldset>
<h2>Upload an image</h2>
<p>
<label>
File:
<%= file_field 'image', 'file' %>
</label>
</p>
</fieldset>

View File

@ -0,0 +1,12 @@
<h1>Editing images</h1>
<%= error_messages_for :images %>
<% form_for(:images, :url => images_path(@images), :html => { :method => :put }) do |f| %>
<p>
<%= submit_tag "Update" %>
</p>
<% end %>
<%= link_to 'Show', images_path(@images) %> |
<%= link_to 'Back', images_path %>

View File

@ -0,0 +1,7 @@
<%= render :partial => 'image', :collection => @images, :locals => { :version => :thumbnail } %>
<%= render :partial => 'shared/pagination_links' %>
<% content_for :sidebar do -%>
<%= new_image_link -%><br />
<% end -%>

View File

@ -0,0 +1,20 @@
<%= error_messages_for :image %>
<% form_for(:images, :url => galleries_path, :html => { :multipart => true, :onsubmit => "$('spinner').style.display = 'inline';" }) do |f| %>
<fieldset>
<h2>Upload an image</h2>
<p>
<label>
File:
<%= file_field 'image', 'file' %>
</label>
</p>
</fieldset>
<p>
<%= submit_tag "Create" %> <%= image_tag '/images/spinner.gif', :id => 'spinner', :style => 'display:none' %>
</p>
<% end %>
<% content_for :sidebar do -%>
<%= new_image_link -%><br />
<% end -%>

View File

@ -0,0 +1,9 @@
<%= render :partial => 'image', :locals => { :image => @image } %>
<br />
<% content_for :sidebar do -%>
<%= link_to "#{@image.people.title}'s images (#{@image.people.images.size})", galleries_path(:id => @image.people_id) -%><br />
<%= link_to "Download original", :action => 'download_original', :id => @image.id -%><br />
<%= link_to 'Destroy', gallery_path(@image), :confirm => 'Are you sure?', :method => :delete %><br />
<%= link_to 'Tag Image', :controller => :tag_images, :action => :show, :id => @image.id -%><br />
<% end -%>

View File

@ -5,6 +5,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<%= stylesheet_link_tag 'application', :media => 'all' %>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag 'control.modal.js' %>
<script type="text/javascript">
<%= yield :script %>
</script>
@ -53,4 +54,5 @@
</div>
</div>
</body>

View File

@ -0,0 +1,30 @@
<div id="<%= window_id -%>_dialog">
<div class="dialogBox">
<div class="dialogHeader"><%= title -%></div>
<br />
<div class="dialogContent"><%= body -%></div>
</div>
</div>
<%= link_to(title, "##{window_id}_dialog", { :class => "#{modal ? '' : 'non'}modal_controls", :onclick => "return false;", :id => "#{window_id}_id_key", :style => 'display: none;' }) %>
<% content_for :script do -%>
addLoadEvent(function(){
if(!window.lightboxes)
lightboxes = {};
if(!window.after_opens)
after_opens = {};
if(!window.before_closes)
before_closes = {}
var link = $("<%= window_id -%>_id_key");
var key = '<%= window_id -%>';
var ao = after_opens[key];
var bc = before_closes[key];
if(ao == undefined)
ao = function(){};
if(bc == undefined)
bc = function(){};
lightboxes[key] = new Control.Modal(link, {
afterOpen: ao, beforeClose: bc,
overlayCloseOnClick: <%= modal ? 'false' : 'true' -%>
});
});
<% end -%>

View File

@ -0,0 +1,46 @@
<% content_for :stylesheet do -%>
#browser_box {
}
#browser_box img {
vertical-align: middle;
}
#browser_controls {
padding: 3px 10px 3px 10px;
text-align: center;
}
<% end -%>
<% content_for :script do -%>
if(!window.after_opens)
after_opens = {};
if(!window.before_closes)
before_closes = {}
after_opens['tagged_image_browser'] = function(){
$('browser_box').hide();
new Ajax.Updater('browser_box',
'<%= url_for(:controller => :tag_images, :action => :tagged_images, :id => ((obj ||= nil).nil? ? nil : obj.id), :tagged_type => obj.class) -%>',
{
onFailure: function() {
lightboxes['tagged_image_browser'].close();
},
onSuccess: function() {
$('browser_spinner').hide();
new Effect.Appear('browser_box', { duration: 1.5 });
}
}
);
}
before_closes['tagged_image_browser'] = function(){
$('browser_box').hide();
$('browser_spinner').show();
}
<% end -%>
<% lightbox :title => 'Tagged Images', :window_id => 'tagged_image_browser' do -%>
<div id="browser_spinner"><div class="centered"><%= image_tag('spinner.gif') -%></div></div>
<div id="browser_box"></div>
<% end -%>

View File

@ -0,0 +1 @@
<%= error_messages_for 'tag_image' %>

View File

@ -0,0 +1,8 @@
<p>
<strong>Tagged items:</strong>
<% if @tag_images.empty? -%>
None.
<% else -%>
<%= @tag_images.collect { |t| "<span onmouseover=\"show_tag_at(#{t.x}, #{t.y})\" onmouseout=\"hide_tag_box()\">#{t.tagged.title} (<em>#{t.tagged_type}</em> | #{link_to_remote('Remove', :url => tag_image_path(t), :update => 'tag_images', :method => :delete)})</span>" }.join(', ') -%>
<% end -%>
</p>

View File

@ -0,0 +1,10 @@
<% @results.keys.each do |ctype| -%>
<% next if @results[ctype].empty? -%>
<h3><%= ctype.pluralize -%></h3>
<p>
<%= @results[ctype].collect { |r| link_to_function(r.title, "set_taggable_item(#{r.id}, '#{r.title}', '#{ctype}');") }.join(', ') %>
</p>
<% end -%>
<% unless @results.detect { |key, val| !val.empty? } -%>
<small>No results found...</small>
<% end -%>

View File

@ -0,0 +1,11 @@
<div class="centered">
<% @tagged_images.each do |tag_image| -%>
<%= link_to(image_tag(tag_image.image.filename_for_version(:thumbnail)), gallery_path(tag_image.image), :popup => true) %>
<% end -%>
</div>
<br />
<div id="browser_controls" class="centered">
<%= image_browser_navigation_link('go-first.png', 1, @page_count, @tagged_type, params[:id]) -%> <%= image_browser_navigation_link('go-previous.png', @current_page - 1, @page_count, @tagged_type, params[:id]) -%> <%= @current_page -%> / <%= @page_count -%> <%= image_browser_navigation_link('go-next.png', @current_page + 1, @page_count, @tagged_type, params[:id]) -%> <%= image_browser_navigation_link('go-last.png', @page_count, @page_count, @tagged_type, params[:id]) -%>
</div>

View File

@ -0,0 +1,110 @@
<% content_for :stylesheet do -%>
#image_block {
z-index: 0;
border: 1px solid black;
padding: 0px;
width: <%= @image.screen_width -%>px;
height: <%= @image.screen_height -%>px;
background-image: url('/images/<%= @image.filename_for_version -%>');
background-repeat: no-repeat;
}
#image_block_container {
margin: 10px <%= (605 - @image.screen_width) / 2 -%>px 20px <%= (605 - @image.screen_width) / 2 -%>px;
}
#image_tag_box {
position: relative;
z-index: 2;
width: 100px;
height: 100px;
border: 5px solid #db3333;
left: 0;
top: 0;
display: none;
}
<% end -%>
<% content_for :script do -%>
function show_tag_at(xcoord, ycoord)
{
$('image_tag_box').style.top = (ycoord - 50) + 'px';
$('image_tag_box').style.left = (xcoord - 50) + 'px';
$('image_tag_box').style.display = 'block';
}
function hide_tag_box()
{
$('image_tag_box').style.display = 'none';
}
function set_coordinates(event)
{
xcoord = (event.offsetX ? event.offsetX : (event.pageX - $('image_block').offsetLeft));
ycoord = (event.offsetY ? event.offsetY : (event.pageY - $('image_block').offsetTop));
show_tag_at(xcoord, ycoord);
lightboxes['taggedContentDialog'].open();
}
function set_taggable_item(id, title, type)
{
$('tag_image_tagged_id').value = id;
$('tag_image_title').innerHTML = title;
$('tag_image_tagged_type').value = type;
}
if(!window.after_opens)
after_opens = {};
if(!window.before_closes)
before_closes = {};
after_opens['taggedContentDialog'] = function(){
$('tag_image_x').value = xcoord;
$('tag_image_y').value = ycoord;
$('tag_image_image_id').value = <%= params[:id] -%>;
$('search').focus();
}
before_closes['taggedContentDialog'] = function(){
hide_tag_box();
}
<% end -%>
<div id="image_block_container" class="centered">
<div id="image_block" onclick='set_coordinates(event);'>
<div id="image_tag_box" style="display: none;"></div>
</div>
<br />
<div id="tag_images">
<%= render :partial => 'tag_images' %>
</div>
</div>
<% lightbox :title => 'Search for a taggable item', :window_id => 'taggedContentDialog' do -%>
<div id="tag_image_errors" class="errorExplanation"></div>
<form id="tag_image_fields">
<%= hidden_field 'tag_image', 'x' %>
<%= hidden_field 'tag_image', 'y' %>
<%= hidden_field 'tag_image', 'image_id' %>
<%= hidden_field 'tag_image', 'tagged_id' %>
<%= hidden_field 'tag_image', 'tagged_type' %>
</form>
<div class="centered">
<%= link_to_function(image_tag('edit-clear.png'), "$('search').value = '';") -%> <%= text_field_tag 'search', '', :size => 30 -%><br />
Selected: <span id="tag_image_title">None</span>
</div>
<div id="taggable_results" class="dialogSearchResults"></div>
<%= observe_field 'search',
:url => { :action => 'taggable_search' },
:frequency => 2,
:update => 'taggable_results',
:with => "'name='+escape(value)" %>
<div class="dialogControls">
<%= link_to_remote('Save', { :url => tag_images_path, :with => "Form.serialize($('tag_image_fields'))", :success => 'Control.Modal.close()', :update => { :success => 'tag_images', :failure => 'tag_image_errors' } }, { :method => :post }) -%>
<%= link_to_function 'Cancel', "Control.Modal.close()" -%>
</div>
<% end -%>
<% content_for :sidebar do -%>
<%= link_to 'Image Details', gallery_path(@image) -%><br />
<% end -%>

View File

@ -9,3 +9,4 @@ end
require 'redcloth'
require 'has_one_page'
require 'has_many_tagged_images'

View File

@ -1,6 +1,8 @@
ActionController::Routing::Routes.draw do |map|
map.resources :tag_images
map.resources :beers, :breweries, :pages, :discussions, :peoples, :roles,
:sessions, :styles, :galleries
:sessions, :styles, :galleries, :tag_images
map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

View File

@ -0,0 +1,26 @@
class CreateImages < ActiveRecord::Migration
def self.up
create_table :images do |t|
t.column :people_id, :integer
t.column :created_at, :datetime
t.column :original, :string
t.column :thumbnail, :string
t.column :screen, :string
t.column :screen_width, :integer
t.column :screen_height, :integer
t.column :content_type, :string
end
add_index :images, :people_id
create_table :images_pages, :id => false do |t|
t.column :image_id, :integer
t.column :page_id, :integer
end
add_index :images_pages, :image_id
add_index :images_pages, :page_id
end
def self.down
drop_table :images
drop_table :images_pages
end
end

View File

@ -0,0 +1,19 @@
class CreateTagImages < ActiveRecord::Migration
def self.up
create_table :tag_images do |t|
t.column :image_id, :integer
t.column :tagged_id, :integer
t.column :tagged_type, :string, :limit => 32
t.column :primary, :boolean
t.column :x, :integer
t.column :y, :integer
end
add_index :tag_images, :image_id
add_index :tag_images, :tagged_id
add_index :tag_images, :tagged_type
end
def self.down
drop_table :tag_images
end
end

View File

@ -5,7 +5,7 @@ base_actions = ApplicationController.action_methods
# rather than defining them here.
controllers = [ PagesController, DiscussionsController, StylesController,
PeoplesController, BeersController, BreweriesController, RolesController,
GalleriesController ]
GalleriesController, TagImagesController ]
controllers.each do |c|
actions = c.action_methods - base_actions
cname = c.controller_name
@ -28,6 +28,11 @@ Permission.find(:all,
next if [ 'new', 'create', 'edit', 'update', 'destroy' ].include?(p.action)
r.permissions << p
end
Permission.find(:all,
:conditions => [ 'controller = ?', 'tag_images' ]).each do |p|
next if [ 'show', 'create', 'destroy', 'taggable_search' ].include?(p.action)
r.permissions << p
end
r2 = Role.admin_role
Permission.find(:all).each do |p|

View File

@ -0,0 +1,25 @@
module ActiveRecord # :nodoc:
class Base # :nodoc:
class << self
##
# This method will add a has_one :page association and a few useful
# callbacks to the requested model. It expects to have a
# :owner_class parameter given so that it knows what the owner class
# name should be. The associated model will automatically be deleted
# when this model is deleted.
#
# The Page will automatically have the title updated from the owner's
# title field and be saved after a successful save. When a Page errors
# on validation, the errors are automatically copied into the owner so
# that the user doesn't even have to know what is going on.
#
def has_many_tagged_images(options = {})
class_eval do
has_many :tagged_images, :source_type => self.base_class.to_s,
:source => :tagged, :through => :tag_images
has_many :tag_images, :dependent => :destroy, :as => :tagged
end
end
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

BIN
public/images/go-first.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

BIN
public/images/go-last.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

BIN
public/images/go-next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

View File

@ -1,3 +1,15 @@
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
function set_all_checkboxes(form_name, field_name, check_value)
{
if(!document.forms[form_name])

View File

@ -6,6 +6,7 @@
@import 'layout.css';
@import 'content.css';
@import 'syntax.css';
@import 'lightboxes.css';
@media print { #sidebar { display: none; }
#content { float: none; width: 90%; }
@ -22,10 +23,10 @@ textarea { font: normal 12px "bitstream vera sans", verdana, sans-serif; }
abbr { border: none; }
cite { font-style: normal; }
a img { border: none; padding: 0; margin: 0; }
a img { border: none; padding: 0; margin: 0; vertical-align: middle; text-decoration: none; }
a:link, a:visited { color: #000; }
a:hover, a:active { color: #fff; background: #000; }
a:link, a:visited { color: #000; text-decoration: none; }
a:hover, a:active { text-decoration: underline; }
/* http://longren.org/2006/09/27/wrapping-text-inside-pre-tags */
pre {
@ -35,3 +36,5 @@ pre {
white-space: -o-pre-wrap;
word-wrap: break-word;
}
.centered { text-align: center; }

View File

@ -2,12 +2,12 @@
Header
--------------------------------------------------------------*/
#header {
#header {
background: url(/images/header_shadow.gif) repeat-x left bottom;
}
#header a:link,
#header a:visited {
#header a:visited {
color: #000;
text-decoration: none;
}
@ -19,14 +19,14 @@
}
#header h1 {
font: bold 410% georgia, serif;
letter-spacing: -1px;
font: bold 410% georgia, serif;
letter-spacing: -1px;
margin: 0;
float: left;
float: left;
}
#header h2 {
font: normal 12px verdana, arial, sans-serif;
font: normal 12px verdana, arial, sans-serif;
margin: 2.35em 0.2em 0 0;
float: right;
}
@ -41,37 +41,37 @@
#content h2,
#content h3,
#content h4,
#content h5 {
#content h5 {
font-family: georgia, times;
font-weight: normal;
letter-spacing: -1px;
}
#content h1 {
#content h1 {
font-size: 28px;
margin: 0 0 0.3em;
margin: 0 0 0.3em;
}
#content h2 {
#content h2 {
font-size: 24px;
margin: 0 0 0.3em;
margin: 0 0 0.3em;
}
#content h3 {
#content h3 {
font-size: 22px;
margin: 1.2em 0 0.3em;
margin: 1.2em 0 0.3em;
}
#content h4 {
#content h4 {
font-size: 20px;
margin: 1.2em 0 0.3em;
margin: 1.2em 0 0.3em;
border-bottom: 1px dotted #bbb;
}
#content h5 {
font-size: 20px;
background: #ffd;
margin: 1.2em 0 0.3em;
margin: 1.2em 0 0.3em;
border-bottom: 1px dotted #aaa;
}
@ -91,17 +91,17 @@
}
#content li {
line-height: 15px;
line-height: 15px;
margin: 0 0 0 1em; padding: 0;
}
#content blockquote {
color: #555;
border-left: 5px solid #ccc;
border-left: 5px solid #ccc;
margin: 1.3em 1em; padding: 0 1em;
}
#content code {
#content code {
font: normal 12px "bitstream vera sans mono", monaco "lucida console", "courier new", courier, serif;
}
@ -114,25 +114,25 @@
}
/* Article Entries - class names based on http://microformats.org/wiki/hatom] */
#content .hentry {
#content .hentry {
margin: 0 0 3em 0;
}
#content .hentry .entry-title {
font-size: 30px;
line-height: 99%;
letter-spacing: -1.5px;
letter-spacing: -1.5px;
margin: 0;
}
#content .hentry .entry-title a:link,
#content .hentry .entry-title a:visited {
#content .hentry .entry-title a:visited {
color: #111;
text-decoration: none;
}
#content .hentry .entry-title a:hover,
#content .hentry .entry-title a:active {
#content .hentry .entry-title a:active {
background: transparent;
text-decoration: underline;
}
@ -163,7 +163,7 @@
#content .hentry .entry-content { }
#content .hentry ul.meta {
#content .hentry ul.meta {
font-size: 10px;
background: #eee;
margin: 0; padding: 5px;
@ -171,7 +171,7 @@
list-style-type: none;
}
#content .hentry ul.meta li {
#content .hentry ul.meta li {
line-height: 13px;
margin: 0; padding: 0;
}
@ -203,7 +203,7 @@
margin: 0 0 1.5em; padding: 1em;
}
#content li.discussion.preview {
#content li.discussion.preview {
background: #ffc;
border: 3px solid #fab444;
margin: 0 0 1.5em; padding: 1em;
@ -239,19 +239,19 @@
padding: 1em 0.5em;
}
#content form.discussions fieldset {
#content form.discussions fieldset {
border: none;
}
#content form.discussions legend {
#content form.discussions legend {
display: none;
}
#content form.discussions label {
#content form.discussions label {
font-weight: bold;
}
#content form.discussions textarea {
#content form.discussions textarea {
width: 90%; height: 150px;
padding: 3px;
}
@ -263,24 +263,32 @@
}
#content .people_image .author {
margin: 0 0 0.5em 0.5em;
width: 60px; height: 60px;
float: right;
margin: 0 2.5em 0.5em 0.5em;
}
#content .people_image .meta {
text-align: center;
}
.people_image img {
margin: 10px;
}
/*--------------------------------------------------------------
Sidebar
--------------------------------------------------------------*/
#sidebar {
font-size: 11px;
font-size: 11px;
}
#sidebar h3 {
#sidebar a:hover, #sidebar a:active {
color: #fff;
background-color: #000;
text-decoration: none;
}
#sidebar h3 {
font: bold 14px "lucidamac bold", "lucida grande", verdana, arial, helvetica, sans-serif;
margin: 0 0 0.5em;
}
@ -296,11 +304,11 @@
}
#sidebar ul {
list-style-type: none;
list-style-type: none;
margin: 0 0 2em; padding: 0;
}
#sidebar li {
#sidebar li {
margin: 0;
padding: 1px 0;
}
@ -308,7 +316,7 @@
#sidebar em { font-style: normal; }
/* Live-search and results */
#sidebar .search p {
#sidebar .search p {
margin: 0;
}
@ -322,7 +330,7 @@
height: 15px;
}
#sidebar .search .results {
#sidebar .search .results {
margin: 0 0 1.25em;
}
@ -330,12 +338,12 @@
margin-top: 1em;
}
#sidebar .search .results p {
#sidebar .search .results p {
font: bold 14px "lucidamac bold", "lucida grande", verdana, arial, helvetica, sans-serif;
margin: 0 0 0.5em;
}
#sidebar .search .results ul {
#sidebar .search .results ul {
margin: 0; padding: 3px;
}
@ -346,53 +354,30 @@
color: #222;
}
#sidebar .search .results a:hover,
#sidebar .search .results a:hover,
#sidebar .search .results a:active {
color: #fff;
}
/* Flickr sidebar-node */
#sidebar #flickr {
margin: 0 0 2em;
clear: both;
}
#sidebar #flickr div {
}
#sidebar #flickr img {
margin: 0 0 5px;
padding: 5px;
border: 1px solid #ddd;
display: block;
}
#sidebar #flickr img:hover {
background: #ffc;
}
#sidebar #flickr a {
border: none;
}
/*--------------------------------------------------------------
Footer
--------------------------------------------------------------*/
#footer {
border-top: 1px solid #ccc;
font-size: 90%;
font-size: 90%;
}
#footer a:link,
#footer a:visited {
#footer a:visited {
color: #000;
}
#footer a:hover,
#footer a:active {
#footer a:active {
color: #fff;
background: #000;
text-decoration: none;
}
#footer hr {
@ -401,21 +386,21 @@
#footer p {
width: 40%;
float: left;
float: left;
margin: 0; padding: 0;
}
#footer ul {
width: 40%;
margin: 0; padding: 0;
list-style-type: none;
text-align: right;
float: right;
list-style-type: none;
text-align: right;
float: right;
}
#footer li {
margin: 0; padding: 0 0 0 1em;
display: inline;
display: inline;
}
/* Tag Cloud Styles */
@ -442,11 +427,11 @@
/* flash hash styles */
#notice {
padding-top: 6px;
padding-bottom: 6px;
padding-bottom: 6px;
padding-left: 6px;
background-color: #F7F4D5;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
border-bottom: 1px solid #666;
color: #333;
font-weight: bold;
margin-bottom: 10px;
@ -454,11 +439,11 @@
#error {
padding-top: 6px;
padding-bottom: 6px;
padding-bottom: 6px;
padding-left: 6px;
background-color: #FFCCCC;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
border-bottom: 1px solid #666;
color: #333;
font-weight: bold;
margin-bottom: 10px;

View File

@ -15,7 +15,7 @@ body {
#container {
max-width: 795px;
text-align: left;
text-align: left;
margin: 0 auto; padding: 10px 0 0 0;
}

View File

@ -0,0 +1,69 @@
.dialogBox {
background-color: #eee;
border: 1px solid #444;
-moz-border-radius: 5px;
padding: 4px;
height: auto;
min-height: 75px;
max-height: 450px;
width: auto;
min-width: 275px;
max-width: 550px;
text-align: left;
}
.dialogHeader {
background: #999;
padding: 2px 5px 2px 5px;
text-align: center;
-moz-border-radius: 5px;
border: 1px solid #444;
color: #FFF;
font-weight: bold;
font-size: 1.2em;
}
.dialogControls {
height: auto;
text-align: center;
width: auto;
font-size: 1.1em;
margin: 10px;
}
.dialogControls a {
background-color: #CCC;
border: 1px solid black;
padding: 0.25em 1.0em 0.25em 1.0em;
text-decoration: none;
-moz-border-radius: 3px;
}
.dialogControls a:hover, .dialogControls a.over {
text-decoration: none;
border: 1px solid black;
background-color: #999;
color: white;
}
.dialogContent {
padding: 0px 10px 0px 10px;
overflow: auto;
}
.dialogSearchResults {
padding: 0.25em;
margin-bottom: 5px;
font-size: 1.1em;
min-height: 50px;
max-height: 300px;
overflow: auto;
}
.dialogSearchResults li {
margin-top: 3px;
margin-bottom: 3px;
}
#modal_overlay {
/* konqueror doesn't like these styles. omit for now.
background-color: #727272;
*/
}

5
test/fixtures/images.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

5
test/fixtures/tag_images.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

View File

@ -0,0 +1,57 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'images_controller'
# Re-raise errors caught by the controller.
class GalleriesController; def rescue_action(e) raise e end; end
class GalleriesControllerTest < Test::Unit::TestCase
fixtures :images
def setup
@controller = GalleriesController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_should_get_index
get :index
assert_response :success
assert assigns(:images)
end
def test_should_get_new
get :new
assert_response :success
end
def test_should_create_images
old_count = Images.count
post :create, :images => { }
assert_equal old_count+1, Images.count
assert_redirected_to images_path(assigns(:images))
end
def test_should_show_images
get :show, :id => 1
assert_response :success
end
def test_should_get_edit
get :edit, :id => 1
assert_response :success
end
def test_should_update_images
put :update, :id => 1, :images => { }
assert_redirected_to images_path(assigns(:images))
end
def test_should_destroy_images
old_count = Images.count
delete :destroy, :id => 1
assert_equal old_count-1, Images.count
assert_redirected_to images_path
end
end

View File

@ -0,0 +1,57 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'tag_images_controller'
# Re-raise errors caught by the controller.
class TagImagesController; def rescue_action(e) raise e end; end
class TagImagesControllerTest < Test::Unit::TestCase
fixtures :tag_images
def setup
@controller = TagImagesController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_should_get_index
get :index
assert_response :success
assert assigns(:tag_images)
end
def test_should_get_new
get :new
assert_response :success
end
def test_should_create_tag_image
old_count = TagImage.count
post :create, :tag_image => { }
assert_equal old_count+1, TagImage.count
assert_redirected_to tag_image_path(assigns(:tag_image))
end
def test_should_show_tag_image
get :show, :id => 1
assert_response :success
end
def test_should_get_edit
get :edit, :id => 1
assert_response :success
end
def test_should_update_tag_image
put :update, :id => 1, :tag_image => { }
assert_redirected_to tag_image_path(assigns(:tag_image))
end
def test_should_destroy_tag_image
old_count = TagImage.count
delete :destroy, :id => 1
assert_equal old_count-1, TagImage.count
assert_redirected_to tag_images_path
end
end

10
test/unit/image_test.rb Normal file
View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ImageTest < Test::Unit::TestCase
fixtures :images
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class TagImageTest < Test::Unit::TestCase
fixtures :tag_images
# Replace this with your real tests.
def test_truth
assert true
end
end