diff --git a/app/controllers/albums_controller.rb b/app/controllers/albums_controller.rb index e8e36d6..a4a08ed 100644 --- a/app/controllers/albums_controller.rb +++ b/app/controllers/albums_controller.rb @@ -16,9 +16,10 @@ class AlbumsController < ApplicationController format.html format.json { render :json => @album } format.xml { render :xml => @album } + format.pdf { render :pdf => @album.title } end end - + def new @album = Album.new end diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index c80ff05..f0373b4 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -2,7 +2,7 @@ class PhotosController < ApplicationController before_filter :require_user, :only => [:new, :create, :edit, :update, :destroy] def index - @photos = Photo.find(:all) + @photos = Tag.find_by_title( params[:tag_id] ).photos respond_to do |format| format.html format.json { render :json => @photos } diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb new file mode 100644 index 0000000..a46d724 --- /dev/null +++ b/app/controllers/tags_controller.rb @@ -0,0 +1,11 @@ +class TagsController < ApplicationController + + def index + @tags = Tag.find( :all) + respond_to do |format| + format.html + format.json { render :json => @tags } + format.xml { render :xml => @tags } + end + end +end diff --git a/app/models/photo.rb b/app/models/photo.rb index c732266..99dc335 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -3,12 +3,30 @@ class Photo < ActiveRecord::Base has_many :photo_tags, :dependent => :destroy has_many :tags, :through => :photo_tags + #accepts_nested_attributes_for :photo_tags, :allow_destroy => true + validates_uniqueness_of :path, :message => "Photo already exsists on disc" + validates_presence_of :title before_destroy :destroy_file + attr_accessor :tag_list + + def tag_list + return self.tags.find(:all, :order => 'title').collect{ |t| t.title }.join(" ") + end + + def tag_list=(tags) + ts = Array.new + tags.split(" ").each do |tag| + ts.push( Tag.find_or_create_by_title( :title => tag) ) + end + self.tags = ts + end + private + def destroy_file puts "DELETE FILE " + APP_CONFIG[:photos_path] + self.path File.delete( APP_CONFIG[:photos_path] + self.path ) if File.exists?( APP_CONFIG[:photos_path] + self.path ) diff --git a/app/models/tag.rb b/app/models/tag.rb index 0cb1278..4b4833f 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -1,4 +1,14 @@ class Tag < ActiveRecord::Base has_many :photo_tags has_many :photos, :through => :photo_tags + + validates_uniqueness_of :title + + def to_param + #{ }"#{id}-#{name.gsub(/[^a-z0-9]+/i, '-')}" + #id.to_s+'-'+name.downcase.gsub(/[^a-z0-9]+/i, '-') + #id.to_s+'-'+name.downcase.gsub(' ', '-') + self.title + end + end diff --git a/app/views/albums/_form.html.erb b/app/views/albums/_form.html.erb index c1cc0c9..b11ee4f 100644 --- a/app/views/albums/_form.html.erb +++ b/app/views/albums/_form.html.erb @@ -1,4 +1,7 @@ <%= form.label :title %>
<%= form.text_field :title %>
+<%= form.label :description %>
+<%= form.text_area :description %>

-<%= @album.path %> \ No newline at end of file +Location on disk: <%= APP_CONFIG[:photos_path] + @album.path %>
+Contains: <%= @album.photos.count %> photos
\ No newline at end of file diff --git a/app/views/albums/show.html.erb b/app/views/albums/show.html.erb index 7e6796b..cf73b42 100644 --- a/app/views/albums/show.html.erb +++ b/app/views/albums/show.html.erb @@ -1,45 +1,30 @@ +<% content_for :head do %> + +<% end %> + +<% content_for :javascript do %> + + + +<% end %> +

<%= @album.title %>

- - - <% if current_user %> <%= link_to "Edit album", edit_album_path( @album )%> <% end %> - - - - \ No newline at end of file +<%= link_to "Generate PDF", album_path( @album, :pdf) %> + +
+
+
+
+
+ +
+ +
+ +

<%= @album.description %>

\ No newline at end of file diff --git a/app/views/albums/show.pdf.erb b/app/views/albums/show.pdf.erb new file mode 100644 index 0000000..8a40e71 --- /dev/null +++ b/app/views/albums/show.pdf.erb @@ -0,0 +1,3 @@ +

<%= @album.title %>

+ +<%= render :partial => @album.photos.find(:all, :limit => 10) %> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2477ac4..b1d6eab 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,11 +6,15 @@ Gallery + <%= yield :head %> + <%= stylesheet_link_tag 'application' %> <%= yield %> +<%= javascript_include_tag 'jquery-1.3.2.js', 'application' %> +<%= yield :javascript %> diff --git a/app/views/photos/_form.html.erb b/app/views/photos/_form.html.erb new file mode 100644 index 0000000..9288519 --- /dev/null +++ b/app/views/photos/_form.html.erb @@ -0,0 +1,11 @@ +<% content_for :javascript do %> + +<% end %> +<%= hidden_field_tag :all_tags, "'#{Tag.find(:all).map { |tag| tag.title }.join('\',\'')}'" %> +<%= form.label :title %>
+<%= form.text_field :title %>
+
+<%= form.label :tag_list %>
+<%= form.text_field :tag_list, { :autocomplete => "off"} %>
+
+

On disk: ~/<%= @photo.path %>

\ No newline at end of file diff --git a/app/views/photos/_photo.html.erb b/app/views/photos/_photo.html.erb index 3c57729..6610334 100644 --- a/app/views/photos/_photo.html.erb +++ b/app/views/photos/_photo.html.erb @@ -1,4 +1,4 @@
  • " title="<%= photo.title %>"> - <%= image_tag APP_CONFIG[:thumbs_path_public] + photo.album.path + "/" + photo.id.to_s + "_small.jpg" %> + <%= image_tag APP_CONFIG[:thumbs_path_public] + photo.album.path + "/" + photo.id.to_s + "_small.jpg", { :id => 'thumb_' + photo.id.to_s } %>
  • diff --git a/app/views/photos/_photo.pdf.erb b/app/views/photos/_photo.pdf.erb new file mode 100644 index 0000000..5eeb707 --- /dev/null +++ b/app/views/photos/_photo.pdf.erb @@ -0,0 +1 @@ +<%= image_tag APP_CONFIG[:photos_path_public] + photo.path %>
    \ No newline at end of file diff --git a/app/views/photos/edit.html.erb b/app/views/photos/edit.html.erb new file mode 100644 index 0000000..a4530ca --- /dev/null +++ b/app/views/photos/edit.html.erb @@ -0,0 +1,10 @@ +

    Edit Photo

    + +<% form_for @photo do |f| %> + <%= f.error_messages %> + <%= render :partial => "form", :object => f %> + <%= f.submit "Update" %> +<% end %> + +<%= image_tag APP_CONFIG[:thumbs_path_public] + @photo.album.path + "/" + @photo.id.to_s + "_large.jpg" %> +
    <%= link_to "All albums", albums_path %> \ No newline at end of file diff --git a/app/views/photos/index.html.erb b/app/views/photos/index.html.erb new file mode 100644 index 0000000..355dbf6 --- /dev/null +++ b/app/views/photos/index.html.erb @@ -0,0 +1,16 @@ + + + + + + + index.html + + + + + + + + diff --git a/app/views/photos/show.html.erb b/app/views/photos/show.html.erb new file mode 100644 index 0000000..20f31a8 --- /dev/null +++ b/app/views/photos/show.html.erb @@ -0,0 +1,7 @@ +

    <%= @photo.title%>

    +<%= image_tag APP_CONFIG[:thumbs_path_public] + @photo.album.path + "/" + @photo.id.to_s + "_large.jpg" %> + +
    +Tagged with: <%= @photo.tag_list %> +

    <%= @photo.description %>

    +
    <%= link_to "All albums", albums_path %> \ No newline at end of file diff --git a/app/views/tags/index.html.erb b/app/views/tags/index.html.erb new file mode 100644 index 0000000..9c3fc27 --- /dev/null +++ b/app/views/tags/index.html.erb @@ -0,0 +1,4 @@ +

    Tags

    +<% for tag in @tags %> +<%= tag.title %> +<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index a6a2495..3807180 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,6 +7,7 @@ ActionController::Routing::Routes.draw do |map| map.logout "logout", :controller => "user_sessions", :action => "destroy" map.resources :photos map.resources :albums + map.resources :tags, :has_many => [ :photos ] #map.connect ':controller/:action/:id' #map.connect ':controller/:action/:id.:format' diff --git a/public/javascripts/application.js b/public/javascripts/application.js index fe45776..b0e4bef 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1,2 +1,41 @@ -// Place your application-specific JavaScript functions and classes here -// This file is automatically included by javascript_include_tag :defaults +jQuery(function($) { + if ( $('ul.gallery').length ) { + $('ul.gallery').galleria( { + clickNext : true, + insert: "#photo_large", + onImage: function ( image, caption, thumb ) { + image.css('display','none').fadeIn() + + thumb.parents('li').siblings().children('img.selected').fadeTo(500,0.3) + thumb.fadeTo('fast',1).addClass('selected') + $( '#photo_metadata' ).html( 'Update photo details' ) + + var scrollable = $("#thumbstrip").scrollable() + scrollable.seekTo( thumb.parents('ul').children().index( thumb.parents('li') ) ) + }, + onThumb: function ( thumb) { + thumb.css({display:'none',opacity: (thumb.parents('li').is('.active') ? '1' : '0.3') }).fadeIn(1500) + } + }) + } + + + if ( $('#thumbstrip').length ) { + $('#thumbstrip').scrollable( { + items : '#thumbs', + clickable: true, + keyboard : false + }) + if ( $('#thumbs li.active').length == 0 ){ + //$('div.scrollable').scrollable().click(0) + $('#thumbs li:first').addClass('active') + } + } + if ( $('#photo_tag_list').length ) { + $('#photo_tag_list').tagSuggest({ + tags: $('#all_tags').val().split('\'') + }) + } + + //$('div.scrollable').scrollable().click( $('#gallery ul').children().index( $('#gallery li.active') ) ) +}); \ No newline at end of file diff --git a/public/javascripts/jquery.mousewheel.3.0.2/ChangeLog.markdown b/public/javascripts/jquery.mousewheel.3.0.2/ChangeLog.markdown new file mode 100755 index 0000000..8dd106b --- /dev/null +++ b/public/javascripts/jquery.mousewheel.3.0.2/ChangeLog.markdown @@ -0,0 +1,49 @@ +# Mouse Wheel ChangeLog + + +# 3.0.2 + +* Fixed delta being opposite value in latest Opera +* No longer fix pageX, pageY for older mozilla browsers +* Removed browser detection +* Cleaned up the code + + +# 3.0.1 + +* Bad release... creating a new release due to plugins.jquery.com issue :( + + +# 3.0 + +* Uses new special events API in jQuery 1.2.2+ +* You can now treat "mousewheel" as a normal event and use .bind, .unbind and .trigger +* Using jQuery.data API for expandos + + +# 2.2 + +* Fixed pageX, pageY, clientX and clientY event properties for Mozilla based browsers + + +# 2.1.1 + +* Updated to work with jQuery 1.1.3 +* Used one instead of bind to do unload event for clean up. + + +# 2.1 + +* Fixed an issue with the unload handler + + +# 2.0 + +* Major reduction in code size and complexity (internals have change a whole lot) + + +# 1.0 + +* Fixed Opera issue +* Fixed an issue with children elements that also have a mousewheel handler +* Added ability to handle multiple handlers \ No newline at end of file diff --git a/public/javascripts/jquery.mousewheel.3.0.2/README.markdown b/public/javascripts/jquery.mousewheel.3.0.2/README.markdown new file mode 100755 index 0000000..7ec6d03 --- /dev/null +++ b/public/javascripts/jquery.mousewheel.3.0.2/README.markdown @@ -0,0 +1,11 @@ +# jQuery Mouse Wheel Plugin + +A jQuery plugin that adds cross-browser mouse wheel support. + +The latest stable release can be downloaded from the [project page](http://plugins.jquery.com/project/mousewheel) + +## License + +The mousewheel plugin is dual licensed *(just like jQuery)* under the [MIT](http://www.opensource.org/licenses/mit-license.php) and [GPL](http://www.opensource.org/licenses/gpl-license.php) licenses. + +Copyright (c) 2009 [Brandon Aaron](http://brandonaaron.net) \ No newline at end of file diff --git a/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.js b/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.js new file mode 100755 index 0000000..b6b89ba --- /dev/null +++ b/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.js @@ -0,0 +1,60 @@ +/*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * + * Version: 3.0.2 + * + * Requires: 1.2.2+ + */ + +(function($) { + +var types = ['DOMMouseScroll', 'mousewheel']; + +$.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) + for ( var i=types.length; i; ) + this.addEventListener( types[--i], handler, false ); + else + this.onmousewheel = handler; + }, + + teardown: function() { + if ( this.removeEventListener ) + for ( var i=types.length; i; ) + this.removeEventListener( types[--i], handler, false ); + else + this.onmousewheel = null; + } +}; + +$.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } +}); + + +function handler(event) { + var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true; + + event = $.event.fix(event || window.event); + event.type = "mousewheel"; + + if ( event.wheelDelta ) delta = event.wheelDelta/120; + if ( event.detail ) delta = -event.detail/3; + + // Add events and delta to the front of the arguments + args.unshift(event, delta); + + return $.event.handle.apply(this, args); +} + +})(jQuery); \ No newline at end of file diff --git a/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.min.js b/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.min.js new file mode 100644 index 0000000..05ebb0a --- /dev/null +++ b/public/javascripts/jquery.mousewheel.3.0.2/jquery.mousewheel.min.js @@ -0,0 +1,11 @@ +/* Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * + * Version: 3.0.2 + * + * Requires: 1.2.2+ + */ +(function(c){var a=["DOMMouseScroll","mousewheel"];c.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var d=a.length;d;){this.addEventListener(a[--d],b,false)}}else{this.onmousewheel=b}},teardown:function(){if(this.removeEventListener){for(var d=a.length;d;){this.removeEventListener(a[--d],b,false)}}else{this.onmousewheel=null}}};c.fn.extend({mousewheel:function(d){return d?this.bind("mousewheel",d):this.trigger("mousewheel")},unmousewheel:function(d){return this.unbind("mousewheel",d)}});function b(f){var d=[].slice.call(arguments,1),g=0,e=true;f=c.event.fix(f||window.event);f.type="mousewheel";if(f.wheelDelta){g=f.wheelDelta/120}if(f.detail){g=-f.detail/3}d.unshift(f,g);return c.event.handle.apply(this,d)}})(jQuery); \ No newline at end of file diff --git a/public/javascripts/jquery.mousewheel.3.0.2/test/index.html b/public/javascripts/jquery.mousewheel.3.0.2/test/index.html new file mode 100755 index 0000000..ce716a4 --- /dev/null +++ b/public/javascripts/jquery.mousewheel.3.0.2/test/index.html @@ -0,0 +1,204 @@ + + + + Testing mousewheel plugin + + + + + + + +

    jQuery mousewheel.js - Test

    +

    + + + + +

    Test1

    +

    Test2

    +

    Test3

    +

    Test4

    +
    +

    Test5

    +
    +

    Test6

    +

    Test7

    +
    +
    + +
    + +
    + + diff --git a/public/javascripts/scrollable/jquery.scrollable-1.0.2.js b/public/javascripts/scrollable/jquery.scrollable-1.0.2.js new file mode 100644 index 0000000..fa99c94 --- /dev/null +++ b/public/javascripts/scrollable/jquery.scrollable-1.0.2.js @@ -0,0 +1,457 @@ +/** + * jquery.scrollable 1.0.2. Put your HTML scroll. + * + * Copyright (c) 2009 Tero Piirainen + * http://flowplayer.org/tools/scrollable.html + * + * Dual licensed under MIT and GPL 2+ licenses + * http://www.opensource.org/licenses + * + * Launch : March 2008 + * Version : 1.0.2 - Tue Feb 24 2009 10:52:08 GMT-0000 (GMT+00:00) + */ +(function($) { + + function fireEvent(opts, name, self, arg) { + var fn = opts[name]; + + if ($.isFunction(fn)) { + try { + return fn.call(self, arg); + + } catch (error) { + if (opts.alert) { + alert("Error calling scrollable." + name + ": " + error); + } else { + throw error; + } + return false; + } + } + return true; + } + + var current = null; + + + // constructor + function Scrollable(root, conf) { + + // current instance + var self = this; + if (!current) { current = self; } + + // horizontal flag + var horizontal = !conf.vertical; + + + // wrap (root elements for items) + var wrap = $(conf.items, root); + + // current index + var index = 0; + + + // get handle to navigational elements + var navi = root.siblings(conf.navi).eq(0); + var prev = root.siblings(conf.prev).eq(0); + var next = root.siblings(conf.next).eq(0); + var prevPage = root.siblings(conf.prevPage).eq(0); + var nextPage = root.siblings(conf.nextPage).eq(0); + + + // methods + $.extend(self, { + + getVersion: function() { + return [1, 0, 1]; + }, + + getIndex: function() { + return index; + }, + + getConf: function() { + return conf; + }, + + getSize: function() { + return self.getItems().size(); + }, + + getPageAmount: function() { + return Math.ceil(this.getSize() / conf.size); + }, + + getPageIndex: function() { + return Math.ceil(index / conf.size); + }, + + getRoot: function() { + return root; + }, + + getItemWrap: function() { + return wrap; + }, + + getItems: function() { + return wrap.children(); + }, + + /* all seeking functions depend on this */ + seekTo: function(i, time, fn) { + + // default speed + time = time || conf.speed; + + // function given as second argument + if ($.isFunction(time)) { + fn = time; + time = conf.speed; + } + + if (i < 0) { i = 0; } + if (i > self.getSize() - conf.size) { return self; } + + var item = self.getItems().eq(i); + if (!item.length) { return self; } + + // onBeforeSeek + if (fireEvent(conf, "onBeforeSeek", self, i) === false) { + return self; + } + + if (horizontal) { + var left = -(item.outerWidth(true) * i); + wrap.animate({left: left}, time, conf.easing, fn ? function() { fn.call(self); } : null); + + } else { + var top = -(item.outerHeight(true) * i); // wrap.offset().top - item.offset().top; + wrap.animate({top: top}, time, conf.easing, fn ? function() { fn.call(self); } : null); + } + + + // navi status update + if (navi.length) { + var klass = conf.activeClass; + var page = Math.ceil(i / conf.size); + page = Math.min(page, navi.children().length - 1); + navi.children().removeClass(klass).eq(page).addClass(klass); + } + + // prev buttons disabled flag + if (i === 0) { + prev.add(prevPage).addClass(conf.disabledClass); + } else { + prev.add(prevPage).removeClass(conf.disabledClass); + } + + // next buttons disabled flag + if (i >= self.getSize() - conf.size) { + next.add(nextPage).addClass(conf.disabledClass); + } else { + next.add(nextPage).removeClass(conf.disabledClass); + } + + current = self; + index = i; + + // onSeek after index being updated + fireEvent(conf, "onSeek", self, i); + + return self; + + }, + + move: function(offset, time, fn) { + var to = index + offset; + if (conf.loop && to > (self.getSize() - conf.size)) { + to = 0; + } + return this.seekTo(to, time, fn); + }, + + next: function(time, fn) { + return this.move(1, time, fn); + }, + + prev: function(time, fn) { + return this.move(-1, time, fn); + }, + + movePage: function(offset, time, fn) { + return this.move(conf.size * offset, time, fn); + }, + + setPage: function(page, time, fn) { + var size = conf.size; + var index = size * page; + var lastPage = index + size >= this.getSize(); + if (lastPage) { + index = this.getSize() - conf.size; + } + return this.seekTo(index, time, fn); + }, + + prevPage: function(time, fn) { + return this.setPage(this.getPageIndex() - 1, time, fn); + }, + + nextPage: function(time, fn) { + return this.setPage(this.getPageIndex() + 1, time, fn); + }, + + begin: function(time, fn) { + return this.seekTo(0, time, fn); + }, + + end: function(time, fn) { + return this.seekTo(this.getSize() - conf.size, time, fn); + }, + + reload: function() { + return load(); + }, + + click: function(index, time, fn) { + + var item = self.getItems().eq(index); + var klass = conf.activeClass; + + if (!item.hasClass(klass) && (index >= 0 || index < this.getSize())) { + self.getItems().removeClass(klass); + item.addClass(klass); + var delta = Math.floor(conf.size / 2); + var to = index - delta; + + // next to last item must work + if (to > self.getSize() - conf.size) { to--; } + + if (to !== index) { + return this.seekTo(to, time, fn); + } + } + + return self; + } + + }); + + + // mousewheel + if ($.isFunction($.fn.mousewheel)) { + root.bind("mousewheel.scrollable", function(e, delta) { + // opera goes to opposite direction + var step = $.browser.opera ? 1 : -1; + + self.move(delta > 0 ? step : -step, 50); + return false; + }); + } + + // prev button + prev.addClass(conf.disabledClass).click(function() { + self.prev(); + }); + + + // next button + next.click(function() { + self.next(); + }); + + // prev page button + nextPage.click(function() { + self.nextPage(); + }); + + + // next page button + prevPage.addClass(conf.disabledClass).click(function() { + self.prevPage(); + }); + + + // keyboard + if (conf.keyboard) { + + // unfortunately window.keypress does not work on IE. + $(window).unbind("keypress.scrollable").bind("keypress.scrollable", function(evt) { + + var el = current; + if (!el) { return; } + + if (horizontal && (evt.keyCode == 37 || evt.keyCode == 39)) { + el.move(evt.keyCode == 37 ? -1 : 1); + return evt.preventDefault(); + } + + if (!horizontal && (evt.keyCode == 38 || evt.keyCode == 40)) { + el.move(evt.keyCode == 38 ? -1 : 1); + return evt.preventDefault(); + } + + return true; + + }); + } + + // navi + function load() { + + navi.each(function() { + + var nav = $(this); + + // generate new entries + if (nav.is(":empty") || nav.data("me") == self) { + + nav.empty(); + nav.data("me", self); + + for (var i = 0; i < self.getPageAmount(); i++) { + + var item = $("<" + conf.naviItem + "/>").attr("href", i).click(function(e) { + var el = $(this); + el.parent().children().removeClass(conf.activeClass); + el.addClass(conf.activeClass); + self.setPage(el.attr("href")); + return e.preventDefault(); + }); + + if (i === 0) { item.addClass(conf.activeClass); } + nav.append(item); + } + + // assign onClick events to existing entries + } else { + + // find a entries first -> syntaxically correct + var els = nav.children(); + + els.each(function(i) { + var item = $(this); + item.attr("href", i); + if (i === 0) { item.addClass(conf.activeClass); } + + item.click(function() { + nav.find("." + conf.activeClass).removeClass(conf.activeClass); + item.addClass(conf.activeClass); + self.setPage(item.attr("href")); + }); + + }); + } + + }); + + + // item.click() + if (conf.clickable) { + self.getItems().each(function(index, arg) { + var el = $(this); + if (!el.data("set")) { + el.bind("click.scrollable", function() { + self.click(index); + }); + el.data("set", true); + } + }); + } + + + // hover + if (conf.hoverClass) { + self.getItems().hover(function() { + $(this).addClass(conf.hoverClass); + }, function() { + $(this).removeClass(conf.hoverClass); + }); + } + + return self; + } + + load(); + + + // interval stuff + var timer = null; + + function setTimer() { + timer = setInterval(function() { + self.next(); + + }, conf.interval); + } + + if (conf.interval > 0) { + + root.hover(function() { + clearInterval(timer); + }, function() { + setTimer(); + }); + + setTimer(); + } + + } + + + // jQuery plugin implementation + jQuery.prototype.scrollable = function(conf) { + + // already constructed --> return API + var api = this.eq(typeof conf == 'number' ? conf : 0).data("scrollable"); + if (api) { return api; } + + + var opts = { + + // basics + size: 5, + vertical:false, + clickable: true, + loop: false, + interval: 0, + speed: 400, + keyboard: true, + + // other + activeClass:'active', + disabledClass: 'disabled', + hoverClass: null, + easing: 'swing', + + // navigational elements + items: '.items', + prev: '.prev', + next: '.next', + prevPage: '.prevPage', + nextPage: '.nextPage', + navi: '.navi', + naviItem: 'a', + + + // callbacks + onBeforeSeek: null, + onSeek: null, + alert: true + }; + + + $.extend(opts, conf); + + this.each(function() { + var el = new Scrollable($(this), opts); + $(this).data("scrollable", el); + }); + + return this; + + }; + + +})(jQuery); diff --git a/public/javascripts/scrollable/jquery.scrollable-1.0.2.min.js b/public/javascripts/scrollable/jquery.scrollable-1.0.2.min.js new file mode 100644 index 0000000..1fa0ad4 --- /dev/null +++ b/public/javascripts/scrollable/jquery.scrollable-1.0.2.min.js @@ -0,0 +1,13 @@ +/** + * jquery.scrollable 1.0.2. Put your HTML scroll. + * + * Copyright (c) 2009 Tero Piirainen + * http://flowplayer.org/tools/scrollable.html + * + * Dual licensed under MIT and GPL 2+ licenses + * http://www.opensource.org/licenses + * + * Launch : March 2008 + * Version : 1.0.2 - Tue Feb 24 2009 10:52:04 GMT-0000 (GMT+00:00) + */ +(function($){function fireEvent(opts,name,self,arg){var fn=opts[name];if($.isFunction(fn)){try{return fn.call(self,arg);}catch(error){if(opts.alert){alert("Error calling scrollable."+name+": "+error);}else{throw error;}return false;}}return true;}var current=null;function Scrollable(root,conf){var self=this;if(!current){current=self;}var horizontal=!conf.vertical;var wrap=$(conf.items,root);var index=0;var navi=root.siblings(conf.navi).eq(0);var prev=root.siblings(conf.prev).eq(0);var next=root.siblings(conf.next).eq(0);var prevPage=root.siblings(conf.prevPage).eq(0);var nextPage=root.siblings(conf.nextPage).eq(0);$.extend(self,{getVersion:function(){return[1,0,1];},getIndex:function(){return index;},getConf:function(){return conf;},getSize:function(){return self.getItems().size();},getPageAmount:function(){return Math.ceil(this.getSize()/conf.size);},getPageIndex:function(){return Math.ceil(index/conf.size);},getRoot:function(){return root;},getItemWrap:function(){return wrap;},getItems:function(){return wrap.children();},seekTo:function(i,time,fn){time=time||conf.speed;if($.isFunction(time)){fn=time;time=conf.speed;}if(i<0){i=0;}if(i>self.getSize()-conf.size){return self;}var item=self.getItems().eq(i);if(!item.length){return self;}if(fireEvent(conf,"onBeforeSeek",self,i)===false){return self;}if(horizontal){var left=-(item.outerWidth(true)*i);wrap.animate({left:left},time,conf.easing,fn?function(){fn.call(self);}:null);}else{var top=-(item.outerHeight(true)*i);wrap.animate({top:top},time,conf.easing,fn?function(){fn.call(self);}:null);}if(navi.length){var klass=conf.activeClass;var page=Math.ceil(i/conf.size);page=Math.min(page,navi.children().length-1);navi.children().removeClass(klass).eq(page).addClass(klass);}if(i===0){prev.add(prevPage).addClass(conf.disabledClass);}else{prev.add(prevPage).removeClass(conf.disabledClass);}if(i>=self.getSize()-conf.size){next.add(nextPage).addClass(conf.disabledClass);}else{next.add(nextPage).removeClass(conf.disabledClass);}current=self;index=i;fireEvent(conf,"onSeek",self,i);return self;},move:function(offset,time,fn){var to=index+offset;if(conf.loop&&to>(self.getSize()-conf.size)){to=0;}return this.seekTo(to,time,fn);},next:function(time,fn){return this.move(1,time,fn);},prev:function(time,fn){return this.move(-1,time,fn);},movePage:function(offset,time,fn){return this.move(conf.size*offset,time,fn);},setPage:function(page,time,fn){var size=conf.size;var index=size*page;var lastPage=index+size>=this.getSize();if(lastPage){index=this.getSize()-conf.size;}return this.seekTo(index,time,fn);},prevPage:function(time,fn){return this.setPage(this.getPageIndex()-1,time,fn);},nextPage:function(time,fn){return this.setPage(this.getPageIndex()+1,time,fn);},begin:function(time,fn){return this.seekTo(0,time,fn);},end:function(time,fn){return this.seekTo(this.getSize()-conf.size,time,fn);},reload:function(){return load();},click:function(index,time,fn){var item=self.getItems().eq(index);var klass=conf.activeClass;if(!item.hasClass(klass)&&(index>=0||indexself.getSize()-conf.size){to--;}if(to!==index){return this.seekTo(to,time,fn);}}return self;}});if($.isFunction($.fn.mousewheel)){root.bind("mousewheel.scrollable",function(e,delta){var step=$.browser.opera?1:-1;self.move(delta>0?step:-step,50);return false;});}prev.addClass(conf.disabledClass).click(function(){self.prev();});next.click(function(){self.next();});nextPage.click(function(){self.nextPage();});prevPage.addClass(conf.disabledClass).click(function(){self.prevPage();});if(conf.keyboard){$(window).unbind("keypress.scrollable").bind("keypress.scrollable",function(evt){var el=current;if(!el){return;}if(horizontal&&(evt.keyCode==37||evt.keyCode==39)){el.move(evt.keyCode==37?-1:1);return evt.preventDefault();}if(!horizontal&&(evt.keyCode==38||evt.keyCode==40)){el.move(evt.keyCode==38?-1:1);return evt.preventDefault();}return true;});}function load(){navi.each(function(){var nav=$(this);if(nav.is(":empty")||nav.data("me")==self){nav.empty();nav.data("me",self);for(var i=0;i").attr("href",i).click(function(e){var el=$(this);el.parent().children().removeClass(conf.activeClass);el.addClass(conf.activeClass);self.setPage(el.attr("href"));return e.preventDefault();});if(i===0){item.addClass(conf.activeClass);}nav.append(item);}}else{var els=nav.children();els.each(function(i){var item=$(this);item.attr("href",i);if(i===0){item.addClass(conf.activeClass);}item.click(function(){nav.find("."+conf.activeClass).removeClass(conf.activeClass);item.addClass(conf.activeClass);self.setPage(item.attr("href"));});});}});if(conf.clickable){self.getItems().each(function(index,arg){var el=$(this);if(!el.data("set")){el.bind("click.scrollable",function(){self.click(index);});el.data("set",true);}});}if(conf.hoverClass){self.getItems().hover(function(){$(this).addClass(conf.hoverClass);},function(){$(this).removeClass(conf.hoverClass);});}return self;}load();var timer=null;function setTimer(){timer=setInterval(function(){self.next();},conf.interval);}if(conf.interval>0){root.hover(function(){clearInterval(timer);},function(){setTimer();});setTimer();}}jQuery.prototype.scrollable=function(conf){var api=this.eq(typeof conf=='number'?conf:0).data("scrollable");if(api){return api;}var opts={size:5,vertical:false,clickable:true,loop:false,interval:0,speed:400,keyboard:true,activeClass:'active',disabledClass:'disabled',hoverClass:null,easing:'swing',items:'.items',prev:'.prev',next:'.next',prevPage:'.prevPage',nextPage:'.nextPage',navi:'.navi',naviItem:'a',onBeforeSeek:null,onSeek:null,alert:true};$.extend(opts,conf);this.each(function(){var el=new Scrollable($(this),opts);$(this).data("scrollable",el);});return this;};})(jQuery); \ No newline at end of file diff --git a/public/javascripts/shortcut/shortcut.js b/public/javascripts/shortcut/shortcut.js new file mode 100644 index 0000000..debaffb --- /dev/null +++ b/public/javascripts/shortcut/shortcut.js @@ -0,0 +1,223 @@ +/** + * http://www.openjs.com/scripts/events/keyboard_shortcuts/ + * Version : 2.01.B + * By Binny V A + * License : BSD + */ +shortcut = { + 'all_shortcuts':{},//All the shortcuts are stored in this array + 'add': function(shortcut_combination,callback,opt) { + //Provide a set of default options + var default_options = { + 'type':'keydown', + 'propagate':false, + 'disable_in_input':false, + 'target':document, + 'keycode':false + } + if(!opt) opt = default_options; + else { + for(var dfo in default_options) { + if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; + } + } + + var ele = opt.target; + if(typeof opt.target == 'string') ele = document.getElementById(opt.target); + var ths = this; + shortcut_combination = shortcut_combination.toLowerCase(); + + //The function to be called at keypress + var func = function(e) { + e = e || window.event; + + if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields + var element; + if(e.target) element=e.target; + else if(e.srcElement) element=e.srcElement; + if(element.nodeType==3) element=element.parentNode; + + if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; + } + + //Find Which key is pressed + if (e.keyCode) code = e.keyCode; + else if (e.which) code = e.which; + var character = String.fromCharCode(code).toLowerCase(); + + if(code == 188) character=","; //If the user presses , when the type is onkeydown + if(code == 190) character="."; //If the user presses , when the type is onkeydown + + var keys = shortcut_combination.split("+"); + //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked + var kp = 0; + + //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken + var shift_nums = { + "`":"~", + "1":"!", + "2":"@", + "3":"#", + "4":"$", + "5":"%", + "6":"^", + "7":"&", + "8":"*", + "9":"(", + "0":")", + "-":"_", + "=":"+", + ";":":", + "'":"\"", + ",":"<", + ".":">", + "/":"?", + "\\":"|" + } + //Special Keys - and their codes + var special_keys = { + 'esc':27, + 'escape':27, + 'tab':9, + 'space':32, + 'return':13, + 'enter':13, + 'backspace':8, + + 'scrolllock':145, + 'scroll_lock':145, + 'scroll':145, + 'capslock':20, + 'caps_lock':20, + 'caps':20, + 'numlock':144, + 'num_lock':144, + 'num':144, + + 'pause':19, + 'break':19, + + 'insert':45, + 'home':36, + 'delete':46, + 'end':35, + + 'pageup':33, + 'page_up':33, + 'pu':33, + + 'pagedown':34, + 'page_down':34, + 'pd':34, + + 'left':37, + 'up':38, + 'right':39, + 'down':40, + + 'f1':112, + 'f2':113, + 'f3':114, + 'f4':115, + 'f5':116, + 'f6':117, + 'f7':118, + 'f8':119, + 'f9':120, + 'f10':121, + 'f11':122, + 'f12':123 + } + + var modifiers = { + shift: { wanted:false, pressed:false}, + ctrl : { wanted:false, pressed:false}, + alt : { wanted:false, pressed:false}, + meta : { wanted:false, pressed:false} //Meta is Mac specific + }; + + if(e.ctrlKey) modifiers.ctrl.pressed = true; + if(e.shiftKey) modifiers.shift.pressed = true; + if(e.altKey) modifiers.alt.pressed = true; + if(e.metaKey) modifiers.meta.pressed = true; + + for(var i=0; k=keys[i],i 1) { //If it is a special key + if(special_keys[k] == code) kp++; + + } else if(opt['keycode']) { + if(opt['keycode'] == code) kp++; + + } else { //The special keys did not match + if(character == k) kp++; + else { + if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase + character = shift_nums[character]; + if(character == k) kp++; + } + } + } + } + + if(kp == keys.length && + modifiers.ctrl.pressed == modifiers.ctrl.wanted && + modifiers.shift.pressed == modifiers.shift.wanted && + modifiers.alt.pressed == modifiers.alt.wanted && + modifiers.meta.pressed == modifiers.meta.wanted) { + callback(e); + + if(!opt['propagate']) { //Stop the event + //e.cancelBubble is supported by IE - this will kill the bubbling process. + e.cancelBubble = true; + e.returnValue = false; + + //e.stopPropagation works in Firefox. + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + } + } + } + this.all_shortcuts[shortcut_combination] = { + 'callback':func, + 'target':ele, + 'event': opt['type'] + }; + //Attach the function with the event + if(ele.addEventListener) ele.addEventListener(opt['type'], func, false); + else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func); + else ele['on'+opt['type']] = func; + }, + + //Remove the shortcut - just specify the shortcut and I will remove the binding + 'remove':function(shortcut_combination) { + shortcut_combination = shortcut_combination.toLowerCase(); + var binding = this.all_shortcuts[shortcut_combination]; + delete(this.all_shortcuts[shortcut_combination]) + if(!binding) return; + var type = binding['event']; + var ele = binding['target']; + var callback = binding['callback']; + + if(ele.detachEvent) ele.detachEvent('on'+type, callback); + else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); + else ele['on'+type] = false; + } +} \ No newline at end of file diff --git a/public/javascripts/tag/tag.js b/public/javascripts/tag/tag.js new file mode 100644 index 0000000..508a806 --- /dev/null +++ b/public/javascripts/tag/tag.js @@ -0,0 +1,267 @@ +/* + @author: remy sharp / http://remysharp.com + @url: http://remysharp.com/2007/12/28/jquery-tag-suggestion/ + @usage: setGlobalTags(['javascript', 'jquery', 'java', 'json']); // applied tags to be used for all implementations + $('input.tags').tagSuggest(options); + + The selector is the element that the user enters their tag list + @params: + matchClass - class applied to the suggestions, defaults to 'tagMatches' + tagContainer - the type of element uses to contain the suggestions, defaults to 'span' + tagWrap - the type of element the suggestions a wrapped in, defaults to 'span' + sort - boolean to force the sorted order of suggestions, defaults to false + url - optional url to get suggestions if setGlobalTags isn't used. Must return array of suggested tags + tags - optional array of tags specific to this instance of element matches + delay - optional sets the delay between keyup and the request - can help throttle ajax requests, defaults to zero delay + separator - optional separator string, defaults to ' ' (Brian J. Cardiff) + @license: Creative Commons License - ShareAlike http://creativecommons.org/licenses/by-sa/3.0/ + @version: 1.4 + @changes: fixed filtering to ajax hits +*/ + +(function ($) { + var globalTags = []; + + // creates a public function within our private code. + // tags can either be an array of strings OR + // array of objects containing a 'tag' attribute + window.setGlobalTags = function(tags /* array */) { + globalTags = getTags(tags); + }; + + function getTags(tags) { + var tag, i, goodTags = []; + for (i = 0; i < tags.length; i++) { + tag = tags[i]; + if (typeof tags[i] == 'object') { + tag = tags[i].tag; + } + goodTags.push(tag.toLowerCase()); + } + + return goodTags; + } + + $.fn.tagSuggest = function (options) { + var defaults = { + 'matchClass' : 'tagMatches', + 'tagContainer' : 'span', + 'tagWrap' : 'span', + 'sort' : true, + 'tags' : null, + 'url' : null, + 'delay' : 0, + 'separator' : ' ' + }; + + var i, tag, userTags = [], settings = $.extend({}, defaults, options); + + if (settings.tags) { + userTags = getTags(settings.tags); + } else { + userTags = globalTags; + } + + return this.each(function () { + var tagsElm = $(this); + var elm = this; + var matches, fromTab = false; + var suggestionsShow = false; + var workingTags = []; + var currentTag = {"position": 0, tag: ""}; + var tagMatches = document.createElement(settings.tagContainer); + + function showSuggestionsDelayed(el, key) { + if (settings.delay) { + if (elm.timer) clearTimeout(elm.timer); + elm.timer = setTimeout(function () { + showSuggestions(el, key); + }, settings.delay); + } else { + showSuggestions(el, key); + } + } + + function showSuggestions(el, key) { + workingTags = el.value.split(settings.separator); + matches = []; + var i, html = '', chosenTags = {}, tagSelected = false; + + // we're looking to complete the tag on currentTag.position (to start with) + currentTag = { position: currentTags.length-1, tag: '' }; + + for (i = 0; i < currentTags.length && i < workingTags.length; i++) { + if (!tagSelected && + currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) { + currentTag = { position: i, tag: workingTags[i].toLowerCase() }; + tagSelected = true; + } + // lookup for filtering out chosen tags + chosenTags[currentTags[i].toLowerCase()] = true; + } + + if (currentTag.tag) { + // collect potential tags + if (settings.url) { + $.ajax({ + 'url' : settings.url, + 'dataType' : 'json', + 'data' : { 'tag' : currentTag.tag }, + 'async' : false, // wait until this is ajax hit is complete before continue + 'success' : function (m) { + matches = m; + } + }); + } else { + for (i = 0; i < userTags.length; i++) { + if (userTags[i].indexOf(currentTag.tag) === 0) { + matches.push(userTags[i]); + } + } + } + + matches = $.grep(matches, function (v, i) { + return !chosenTags[v.toLowerCase()]; + }); + + if (settings.sort) { + matches = matches.sort(); + } + + for (i = 0; i < matches.length; i++) { + html += '<' + settings.tagWrap + ' class="_tag_suggestion">' + matches[i] + ''; + } + + tagMatches.html(html); + suggestionsShow = !!(matches.length); + } else { + hideSuggestions(); + } + } + + function hideSuggestions() { + tagMatches.empty(); + matches = []; + suggestionsShow = false; + } + + function setSelection() { + var v = tagsElm.val(); + + // tweak for hintted elements + // http://remysharp.com/2007/01/25/jquery-tutorial-text-box-hints/ + if (v == tagsElm.attr('title') && tagsElm.is('.hint')) v = ''; + + currentTags = v.split(settings.separator); + hideSuggestions(); + } + + function chooseTag(tag) { + var i, index; + for (i = 0; i < currentTags.length; i++) { + if (currentTags[i].toLowerCase() != workingTags[i].toLowerCase()) { + index = i; + break; + } + } + + if (index == workingTags.length - 1) tag = tag + settings.separator; + + workingTags[i] = tag; + + tagsElm.val(workingTags.join(settings.separator)); + tagsElm.blur().focus(); + setSelection(); + } + + function handleKeys(ev) { + fromTab = false; + var type = ev.type; + var resetSelection = false; + + switch (ev.keyCode) { + case 37: // ignore cases (arrow keys) + case 38: + case 39: + case 40: { + hideSuggestions(); + return true; + } + case 224: + case 17: + case 16: + case 18: { + return true; + } + + case 8: { + // delete - hide selections if we're empty + if (this.value == '') { + hideSuggestions(); + setSelection(); + return true; + } else { + type = 'keyup'; // allow drop through + resetSelection = true; + showSuggestionsDelayed(this); + } + break; + } + + case 9: // return and tab + case 13: { + if (suggestionsShow) { + // complete + chooseTag(matches[0]); + + fromTab = true; + return false; + } else { + return true; + } + } + case 27: { + hideSuggestions(); + setSelection(); + return true; + } + case 32: { + setSelection(); + return true; + } + } + + if (type == 'keyup') { + switch (ev.charCode) { + case 9: + case 13: { + return true; + } + } + + if (resetSelection) { + setSelection(); + } + showSuggestionsDelayed(this, ev.charCode); + } + } + + tagsElm.after(tagMatches).keypress(handleKeys).keyup(handleKeys).blur(function () { + if (fromTab == true || suggestionsShow) { // tweak to support tab selection for Opera & IE + fromTab = false; + tagsElm.focus(); + } + }); + + // replace with jQuery version + tagMatches = $(tagMatches).click(function (ev) { + if (ev.target.nodeName == settings.tagWrap.toUpperCase() && $(ev.target).is('._tag_suggestion')) { + chooseTag(ev.target.innerHTML); + } + }).addClass(settings.matchClass); + + // initialise + setSelection(); + }); + }; +})(jQuery); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css new file mode 100644 index 0000000..0075040 --- /dev/null +++ b/public/stylesheets/application.css @@ -0,0 +1,55 @@ +.galleria_container { + margin:0 auto; + text-align: center; +} + +#thumbstrip { + + /* required settings */ + position:relative; + overflow:hidden; + width: 602px; + height: 85px; + margin: 0 auto; + padding: 0; + background-color: #000000; +} + +#thumbstrip ul { + /* this cannot be too large */ + width:20000em; + position:absolute; + list-style-type: none; + margin: 0; + padding: 0; +} + +/* + a single item. must be floated on horizontal scrolling + typically this element is the one that *you* will style + the most. +*/ +#thumbstrip ul li { + float:left; + background: black; + padding: 5px; + margin: 0; + cursor: pointer; +} + + +#photo_large img { + border: 1px solid black; +} + +SPAN.tagMatches { + margin-left: 10px; +} + +SPAN.tagMatches SPAN { + padding: 2px; + margin-right: 4px; + background-color: #0000AB; + color: #fff; + cursor: pointer; +} \ No newline at end of file