From 33e3821e61174b47107d9fbcd204d259697b7bdb Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Mon, 14 May 2012 08:59:53 +0800 Subject: [PATCH 01/11] Rake task to change design_ids --- lib/tasks/designs.rake | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/tasks/designs.rake diff --git a/lib/tasks/designs.rake b/lib/tasks/designs.rake new file mode 100644 index 00000000..e59f263a --- /dev/null +++ b/lib/tasks/designs.rake @@ -0,0 +1,11 @@ +# encoding: utf-8 + +namespace :designs do + task :change_to, [:design_id] => [:environment] do |t, args| + # design = Design.find(args[:design_id]) + Page.all.each do |page| + page.update_attribute('design_id', args[:design_id]) + end + end + +end \ No newline at end of file From f0dd1eb8713e9daf22211a68d1484bd4eff966c2 Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Mon, 14 May 2012 09:30:49 +0800 Subject: [PATCH 02/11] Fix designs.rake --- lib/tasks/designs.rake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/tasks/designs.rake b/lib/tasks/designs.rake index e59f263a..c8940e22 100644 --- a/lib/tasks/designs.rake +++ b/lib/tasks/designs.rake @@ -2,9 +2,10 @@ namespace :designs do task :change_to, [:design_id] => [:environment] do |t, args| - # design = Design.find(args[:design_id]) + design = Design.find(args[:design_id]) + theme_id = design.themes.first.id Page.all.each do |page| - page.update_attribute('design_id', args[:design_id]) + page.update_attributes({design_id: args[:design_id], theme_id: (theme_id unless page.root?)}) end end From 9ba4c2cf2fc27139b377fcd75c18ae35e7929690 Mon Sep 17 00:00:00 2001 From: "Matthew K. Fu JuYuan" Date: Mon, 14 May 2012 12:34:15 +0800 Subject: [PATCH 03/11] Change to apply page to had object_auth --- .../object_auths_new_interface_controller.rb | 5 +++-- app/controllers/application_controller.rb | 4 ++++ app/controllers/orbit_backend_controller.rb | 1 + app/helpers/admin/page_content_helper.rb | 18 ++++++++++++++++++ app/models/ad_banner.rb | 10 ++++++++++ app/models/object_auth.rb | 12 ++++++++++++ app/models/user/user.rb | 8 ++++++++ .../setting.html.erb | 2 +- config/locales/zh_tw.yml | 4 ++++ lib/orbit_core_lib.rb | 5 +++++ .../app/models/bulletin_category.rb | 2 +- .../news/app/models/news_bulletin_category.rb | 3 ++- .../back_end/page_contexts_controller.rb | 7 ++++--- .../page_content/app/models/page_context.rb | 12 +++++++++++- .../page_contexts/_page_context.html.erb | 13 +++++++++++-- 15 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 app/helpers/admin/page_content_helper.rb diff --git a/app/controllers/admin/object_auths_new_interface_controller.rb b/app/controllers/admin/object_auths_new_interface_controller.rb index 427ec722..6c33ff71 100644 --- a/app/controllers/admin/object_auths_new_interface_controller.rb +++ b/app/controllers/admin/object_auths_new_interface_controller.rb @@ -1,4 +1,4 @@ -class Admin::ObjectAuthsNewInterfaceController < ApplicationController +class Admin::ObjectAuthsNewInterfaceController < OrbitBackendController include OrbitCoreLib::PermissionUnility layout "new_admin" before_filter :force_order @@ -33,8 +33,9 @@ class Admin::ObjectAuthsNewInterfaceController < ApplicationController protected def update_setting_by_params + user_sat = [] oa = ObjectAuth.find params[:ob_auth][:id] - user_sat = User.find params[:users].keys + user_sat += User.find params[:users].keys if params.has_key? :users users_to_new = user_sat - oa.auth_users users_to_remove = oa.auth_users - user_sat diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4d8d6d48..1455619a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -8,6 +8,10 @@ class ApplicationController < ActionController::Base helper :all before_filter :set_locale, :set_site + def set_current_user + User.current = current_user + end + def front_end_available(module_app_title='') app_controller = ModuleApp.first(conditions: {:key => module_app_title} ) unless app_controller.enable_frontend? diff --git a/app/controllers/orbit_backend_controller.rb b/app/controllers/orbit_backend_controller.rb index bdddfb43..cfa46847 100644 --- a/app/controllers/orbit_backend_controller.rb +++ b/app/controllers/orbit_backend_controller.rb @@ -10,6 +10,7 @@ class OrbitBackendController< ApplicationController def setup_vars @app_title = request.fullpath.split('/')[2] + @app_title = request.fullpath.split('/')[1] if(@app_title == "back_end") @module_app = ModuleApp.first(conditions: {:key => @app_title} ) end diff --git a/app/helpers/admin/page_content_helper.rb b/app/helpers/admin/page_content_helper.rb new file mode 100644 index 00000000..9f2b22e0 --- /dev/null +++ b/app/helpers/admin/page_content_helper.rb @@ -0,0 +1,18 @@ +module Admin::PageContentHelper +include ActionView::Helpers::UrlHelper + + + def show_page_context_edit_auth_link(page_context) + type = 'Edit' +# debugger +# a=1 + oa = page_context.get_object_auth_by_title(type) + if oa.nil? + page_context.object_auths.new(title: type ).save rescue + oa = page_context.get_object_auth_by_title(type) + end +# link_to t('announcement.bulletin.cate_auth'), edit_admin_object_auth_path(oa) + link_to t('admin.page_context.ob_auth.edit'),admin_object_auth_ob_auth_path(oa) + end + +end \ No newline at end of file diff --git a/app/models/ad_banner.rb b/app/models/ad_banner.rb index 39112960..e1013c2f 100644 --- a/app/models/ad_banner.rb +++ b/app/models/ad_banner.rb @@ -1,4 +1,5 @@ class AdBanner + include OrbitCoreLib::ObjectAuthable include Mongoid::Document include Mongoid::Timestamps include Mongoid::MultiParameterAttributes @@ -14,6 +15,15 @@ class AdBanner FX_TYPES = ["blindX","blindY","blindZ","cover","curtainX","curtainY","fade","fadeZoom","growX","growY","scrollUp","scrollDown","scrollLeft","scrollRight","scrollHorz","scrollVert","shuffle","slideX","slideY","toss","turnUp","turnDown","turnLeft","turnRight","uncover","wipe","zoom"] attr_writer :transition_sec + + AfterObjectAuthUrl = '/panel/page_content/back_end/page_contexts' + APP_NAME = 'ad_banners' + ObjectAuthTitlesOptions = %W{edit} + + def pp_object + title + end + def transition_sec self.transition_msec/1000 rescue nil end diff --git a/app/models/object_auth.rb b/app/models/object_auth.rb index ae3fd701..d1c1c673 100644 --- a/app/models/object_auth.rb +++ b/app/models/object_auth.rb @@ -2,6 +2,7 @@ class ObjectAuth < PrototypeAuth include OrbitCoreLib::ObjectTokenUnility validates_uniqueness_of :title ,:scope => [:obj_authable_type,:obj_authable_id] #{ |c| } belongs_to :obj_authable, polymorphic: true + after_save :check_user_has_app_auth # > - Something.find_with_auth(query) # > - or Something.find(query).auth def auth_obj @@ -9,4 +10,15 @@ class ObjectAuth < PrototypeAuth class_obj.find self.obj_authable_id end + def check_user_has_app_auth + sub_managing_users = auth_obj.app_auth.sub_managing_users + app_auth = auth_obj.app_auth + self.auth_users.each do |auth_user| + if !sub_managing_users.include? auth_user && !auth_user.admin? + app_auth.assign_sub_manager(auth_user,User.current) + app_auth.save! + end + end + end + end \ No newline at end of file diff --git a/app/models/user/user.rb b/app/models/user/user.rb index d74a543b..2a2dbd86 100644 --- a/app/models/user/user.rb +++ b/app/models/user/user.rb @@ -20,6 +20,14 @@ class User belongs_to :role has_and_belongs_to_many :sub_roles accepts_nested_attributes_for :attribute_values, :allow_destroy => true + + def self.current + Thread.current[:user] + end + + def self.current=(user) + Thread.current[:user] = user + end def avb_apps sub_role_ids_ary=self.sub_roles.collect{|t| t.id} diff --git a/app/views/admin/object_auths_new_interface/setting.html.erb b/app/views/admin/object_auths_new_interface/setting.html.erb index ae5096ba..f5d17370 100644 --- a/app/views/admin/object_auths_new_interface/setting.html.erb +++ b/app/views/admin/object_auths_new_interface/setting.html.erb @@ -27,7 +27,7 @@
<%= content_tag :div do -%> <% form_tag admin_object_auth_ob_auth_path do %> - <%#= render :partial => "privilege_user", :locals => {:users => @users_array} %> + <%= render :partial => "privilege_user", :locals => {:users => @users_array} %>
<%= submit_tag "Update", :class => 'btn btn-primary' %>
diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml index feb8e83a..7c2ac289 100644 --- a/config/locales/zh_tw.yml +++ b/config/locales/zh_tw.yml @@ -199,6 +199,10 @@ zh_tw: options: 選項 orig_upload_file: 原上傳檔名 page: 頁面管理 + page_context: + edit: 編輯 + ob_auth: + edit: 分類授權 page_part_kinds: text: 文字區塊 public_r_tag: 系統模塊 diff --git a/lib/orbit_core_lib.rb b/lib/orbit_core_lib.rb index 281654a7..a6c9917f 100644 --- a/lib/orbit_core_lib.rb +++ b/lib/orbit_core_lib.rb @@ -19,6 +19,10 @@ module OrbitCoreLib end + def app_auth + ModuleApp.first(conditions: {:title => self.class::APP_NAME} ) + end + def pp_object "Object Auth method 'pp_object' need to be defined for class #{self.class}" end @@ -67,6 +71,7 @@ module OrbitCoreLib module PermissionUnility private def check_permission(type = :use) + setup_vars permission_grant = current_user.admin?? true : false module_app = @module_app.nil?? find_module_app_by_token(params[:token]) : @module_app unless permission_grant diff --git a/vendor/built_in_modules/announcement/app/models/bulletin_category.rb b/vendor/built_in_modules/announcement/app/models/bulletin_category.rb index 955d5f18..ff0e0a9c 100644 --- a/vendor/built_in_modules/announcement/app/models/bulletin_category.rb +++ b/vendor/built_in_modules/announcement/app/models/bulletin_category.rb @@ -10,7 +10,7 @@ class BulletinCategory # include Mongoid::MultiParameterAttributes PAYMENT_TYPES = [ "List", "Picture" ] - + APP_NAME = 'Announcement' field :key field :display diff --git a/vendor/built_in_modules/news/app/models/news_bulletin_category.rb b/vendor/built_in_modules/news/app/models/news_bulletin_category.rb index b3dd7bc4..a9997b99 100644 --- a/vendor/built_in_modules/news/app/models/news_bulletin_category.rb +++ b/vendor/built_in_modules/news/app/models/news_bulletin_category.rb @@ -10,7 +10,8 @@ class NewsBulletinCategory # include Mongoid::MultiParameterAttributes PAYMENT_TYPES = [ "List", "Picture" ] - + APP_NAME = 'news' + field :key field :display diff --git a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/back_end/page_contexts_controller.rb b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/back_end/page_contexts_controller.rb index 6ae315fb..809bfbb3 100644 --- a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/back_end/page_contexts_controller.rb +++ b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/back_end/page_contexts_controller.rb @@ -1,7 +1,8 @@ class Panel::PageContent::BackEnd::PageContextsController < OrbitBackendController - - before_filter :authenticate_user! - before_filter :is_admin? + before_filter :for_app_manager,:except => [:index,:edit,:update,:view,:show] + before_filter :for_app_sub_manager,:only => [:edit,:update,:view,:show] + :authenticate_user! + #before_filter :is_admin? def index diff --git a/vendor/built_in_modules/page_content/app/models/page_context.rb b/vendor/built_in_modules/page_content/app/models/page_context.rb index 5c096711..dfbfd4a9 100644 --- a/vendor/built_in_modules/page_content/app/models/page_context.rb +++ b/vendor/built_in_modules/page_content/app/models/page_context.rb @@ -4,7 +4,9 @@ class PageContext include Mongoid::Document include Mongoid::Timestamps include Mongoid::MultiParameterAttributes - + + include OrbitCoreLib::ObjectAuthable + has_one :context, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy @@ -17,6 +19,14 @@ class PageContext belongs_to :page + AfterObjectAuthUrl = '/panel/page_content/back_end/page_contexts' + APP_NAME = 'page_content' + ObjectAuthTitlesOptions = %W{Edit} + + def pp_object + page.i18n_variable[I18n.locale] + end + def is_top? self.is_top end diff --git a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb index 81b31b93..d2f5cfa1 100644 --- a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb +++ b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb @@ -4,11 +4,20 @@ <%= page_context.page.path %>
- <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %> + + <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%> + <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %> + <%end -%> + <%= page_context.updated_at.strftime("%Y-%m-%d %H:%I:%S") %> <%= User.find(page_context.create_user_id).name %> \ No newline at end of file From 8fcb8ef8def264ada2ae9287e3909c3f3ba329cd Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Mon, 14 May 2012 21:43:10 +0800 Subject: [PATCH 04/11] Change the way links are displayed in menus in front-end --- app/controllers/pages_controller.rb | 2 +- app/models/link.rb | 12 +++++++++++- lib/parsers/parser_common.rb | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 45299450..52c39c01 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -21,7 +21,7 @@ class PagesController < ApplicationController when 'Page' render_page when 'Link' - redirect_to "http://#{@item[:url]}" + redirect_to @item[:url] end else render :file => "#{Rails.root}/public/404.html", :status => :not_found diff --git a/app/models/link.rb b/app/models/link.rb index 4301ead4..5b8c15a6 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -2,10 +2,20 @@ class Link < Item field :url - validates_presence_of :url + validates :url, :presence => true, :format => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix + + before_validation :add_http def link ApplicationController.helpers.link_to(self.name, self.url) end + + protected + + def add_http + unless self.url[/^http?s:\/\//] + self.url = 'http://' + self.url + end + end end diff --git a/lib/parsers/parser_common.rb b/lib/parsers/parser_common.rb index d727e68c..32820a6f 100644 --- a/lib/parsers/parser_common.rb +++ b/lib/parsers/parser_common.rb @@ -27,7 +27,8 @@ module ParserCommon res << "_#{i}" if i res << " active" if (current_page.id.eql?(page.id) || current_page.descendant_of?(page)) res << "'>" - res << "#{page.i18n_variable[I18n.locale]}" + root = "/" + res << "#{page.i18n_variable[I18n.locale]}" if page.visible_children.size > 0 && current <= menu.levels res << "" res << menu_level(page, current_page, current + 1, menu, edit) @@ -119,7 +120,8 @@ module ParserCommon res << "" From 53a0fbddf0d2c310c914e40be7668b7594e38e2e Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 21:25:51 +0800 Subject: [PATCH 05/11] Fix commit error --- app/assets/javascripts/inc/search.js | 36 ++++++++++++++++++++++++++++ app/models/ad_banner.rb | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/inc/search.js diff --git a/app/assets/javascripts/inc/search.js b/app/assets/javascripts/inc/search.js new file mode 100644 index 00000000..50f3835f --- /dev/null +++ b/app/assets/javascripts/inc/search.js @@ -0,0 +1,36 @@ +// JavaScript Document + +// can copy code to any of ur desired javascsript + +//extended jquery to search fast. +$.extend($.expr[':'], { + 'containsi': function (elem, i, match, array) { + return (elem.textContent || elem.innerText || '').toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0; + } +}); +var interval,sval; +$(document).ready(function(){ + $("#user_filter").keyup(function(e){ + if((e.which>96 && e.which<123) || (e.which>64 && e.which<92) || (e.which == 32) || (e.which == 8)){ + sval = $(this).val(); + $(".checkbox").popover("hide"); + $("div.checkblock").hide(); + clearInterval(interval); + interval = setInterval(waitForSearch,1000); + } + }) +}) +var waitForSearch = function(){ + if(sval){ + var totalfoundbyname = $("div#users_checkbox_ary label.member-name:containsi("+sval+")").length + if(totalfoundbyname!=0){ + $("div#users_checkbox_ary label.member-name:containsi("+sval+")").parent().parent().show(); + }else if(totalfoundbyname==0){ + $("div#users_checkbox_ary div.for_unit:containsi("+sval+")").parent().show(); + } + }else{ + $(".checkbox").popover('hide'); + $("div.checkblock").show(); + } + clearInterval(interval); +} \ No newline at end of file diff --git a/app/models/ad_banner.rb b/app/models/ad_banner.rb index e1013c2f..09aa79c8 100644 --- a/app/models/ad_banner.rb +++ b/app/models/ad_banner.rb @@ -1,8 +1,8 @@ class AdBanner - include OrbitCoreLib::ObjectAuthable include Mongoid::Document include Mongoid::Timestamps include Mongoid::MultiParameterAttributes + include OrbitCoreLib::ObjectAuthable field :title field :transition_msec,type: Integer From e54f4409fb4b9605a433f99e5edeb7913d0b259c Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 21:26:09 +0800 Subject: [PATCH 06/11] Add impressionist --- Gemfile | 2 + Gemfile.lock | 9 ++ .../admin/dashboards_controller.rb | 14 ++ .../object_auths_new_interface_controller.rb | 4 +- app/controllers/pages_controller.rb | 32 ++--- app/helpers/admin/dashboard_helper.rb | 16 +-- app/helpers/application_helper.rb | 25 +++- app/models/page.rb | 4 + app/views/admin/dashboards/index.html.erb | 133 ++++++++++++------ config/initializers/impression.rb | 6 + config/locales/en.yml | 6 + config/locales/zh_tw.yml | 14 +- .../front_end/bulletins_controller.rb | 1 + .../announcement/app/models/bulletin.rb | 4 + .../front_end/bulletins/show.html.erb | 1 + .../front_end/news_bulletins_controller.rb | 1 + .../news/app/models/news_bulletin.rb | 6 +- .../front_end/news_bulletins/show.html.erb | 1 + .../front_end/page_contexts_controller.rb | 8 +- .../page_content/app/models/page_context.rb | 6 +- .../front_end/page_contexts/index.html.erb | 2 + 21 files changed, 212 insertions(+), 83 deletions(-) create mode 100644 config/initializers/impression.rb diff --git a/Gemfile b/Gemfile index c24754c6..7e63e353 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,8 @@ gem 'sprockets' gem 'tinymce-rails' gem 'therubyracer' if RUBY_PLATFORM.downcase.include?("linux") +gem "impressionist", :require => "impressionist", :path => "vendor/impressionist" + # Gems used only for assets and not required # in production environments by default. group :assets do diff --git a/Gemfile.lock b/Gemfile.lock index 0dfca3b6..f6bc8bfd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,13 @@ GIT activesupport (>= 3.0.0) railties (>= 3.0.0) +PATH + remote: vendor/impressionist + specs: + impressionist (1.1.1) + httpclient (~> 2.2) + nokogiri (~> 1.5) + GEM remote: http://rubygems.org/ specs: @@ -92,6 +99,7 @@ GEM hike (1.2.1) hoe (2.16.1) rake (~> 0.8) + httpclient (2.2.5) i18n (0.6.0) jquery-rails (1.0.19) railties (~> 3.0) @@ -273,6 +281,7 @@ DEPENDENCIES exception_notification execjs factory_girl_rails + impressionist! jquery-rails jquery-ui-rails kaminari! diff --git a/app/controllers/admin/dashboards_controller.rb b/app/controllers/admin/dashboards_controller.rb index 6bfb91ba..50bbd1aa 100644 --- a/app/controllers/admin/dashboards_controller.rb +++ b/app/controllers/admin/dashboards_controller.rb @@ -7,6 +7,7 @@ class Admin::DashboardsController < ApplicationController def index @module_app_contents, @module_app_contents_total = get_module_app_count('bulletin', 'news_bulletin', 'page_context', 'web_link') @recent_updated = get_recently_updated('bulletin', 'news_bulletin', 'page_context', 'web_link') + @most_visited = get_most_visited('bulletin', 'news_bulletin', 'page_context') end protected @@ -35,5 +36,18 @@ class Admin::DashboardsController < ApplicationController sorted_objects = a.sort {|a,b| b[1]<=>a[1]} sorted_objects[0..9] end + + def get_most_visited(*args) + a = {} + args.each do |module_app| + module_app_class = module_app.classify.constantize + objects = module_app_class.order_by(:view_count, :desc).limit(10) + objects.each do |object| + a.merge!(object => object.view_count) if object.view_count > 0 + end + end + sorted_objects = a.sort {|a,b| b[1]<=>a[1]} + sorted_objects[0..9] + end end diff --git a/app/controllers/admin/object_auths_new_interface_controller.rb b/app/controllers/admin/object_auths_new_interface_controller.rb index 6c33ff71..6018413b 100644 --- a/app/controllers/admin/object_auths_new_interface_controller.rb +++ b/app/controllers/admin/object_auths_new_interface_controller.rb @@ -1,9 +1,7 @@ class Admin::ObjectAuthsNewInterfaceController < OrbitBackendController include OrbitCoreLib::PermissionUnility - layout "new_admin" - before_filter :force_order - layout "new_admin" + before_filter :force_order def setting diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 52c39c01..8758563e 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -7,6 +7,7 @@ class PagesController < ApplicationController def index @item = Page.find_by_name('home') if @item + impressionist(@item) render_page else render :text => 'You need a home page' @@ -14,18 +15,19 @@ class PagesController < ApplicationController end def show - #begin - @item = Item.first(:conditions => {:path => params[:page_name]}) - if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s)) - case @item._type - when 'Page' - render_page - when 'Link' - redirect_to @item[:url] - end - else - render :file => "#{Rails.root}/public/404.html", :status => :not_found - end + #begin + @item = Item.first(:conditions => {:path => params[:page_name]}) + if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s)) + impressionist(@item) + case @item._type + when 'Page' + render_page + when 'Link' + redirect_to @item[:url] + end + else + render :file => "#{Rails.root}/public/404.html", :status => :not_found + end #rescue # render :file => "#{Rails.root}/public/404.html", :status => :not_found #end @@ -55,11 +57,7 @@ class PagesController < ApplicationController def get_item module_app = ModuleApp.first(:conditions => {:key => params[:app_name]}) - # if params[:category_id] - # @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action], :category => params[:category_id]}) - # else - @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action]}) - # end + @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action]}) end end diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb index 4b750fb8..f69185f3 100644 --- a/app/helpers/admin/dashboard_helper.rb +++ b/app/helpers/admin/dashboard_helper.rb @@ -3,26 +3,26 @@ module Admin::DashboardHelper def get_link(title) case title when 'bulletin' - panel_announcement_back_end_bulletins_path + panel_announcement_front_end_bulletins_path when 'news_bulletin' - panel_news_back_end_news_bulletins_path + panel_news_front_end_news_bulletins_path when'page_context' - panel_page_content_back_end_page_contexts_path + panel_page_content_front_end_page_contexts_path when'web_link' - panel_web_resource_back_end_web_links_path + panel_web_resource_front_end_web_links_path end end def get_link_to_object(object) case object._type.underscore when 'bulletin' - panel_announcement_back_end_bulletin_path(object) + panel_announcement_front_end_bulletin_path(object) when 'news_bulletin' - panel_news_back_end_news_bulletin_path(object) + panel_news_front_end_news_bulletin_path(object) when'page_context' - panel_page_content_back_end_page_context_path(object) + panel_page_content_front_end_page_context_path(object) when'web_link' - panel_web_resource_back_end_web_link_path(object) + panel_web_resource_front_end_web_link_path(object) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d04d8c59..5f693965 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -96,7 +96,6 @@ module ApplicationHelper end def visible_for_controllers(*controller_names) - puts controller_names (controller_names.include?(controller.controller_name) || controller_names.include?(request.fullpath)) ? '' : 'hide' end @@ -173,4 +172,28 @@ module ApplicationHelper locale.to_sym == I18n.locale ? 'active in': '' end + def dislpay_view_count(object) + "#{t(:view_count)}: #{object.view_count}" + end + + def display_visitors(options={}) + Impression.where(options).distinct(:session_hash).count + end + + def display_visitors_today + display_visitors(created_at: {'$gte' => Date.today.beginning_of_day, '$lte' => Date.today.end_of_day}) + end + + def display_visitors_this_week + display_visitors(created_at: {'$gte' => Date.today.beginning_of_week, '$lte' => Date.today.end_of_week}) + end + + def display_visitors_this_month + display_visitors(created_at: {'$gte' => Date.today.beginning_of_month, '$lte' => Date.today.end_of_month}) + end + + def display_visitors_this_year + display_visitors(created_at: {'$gte' => Date.today.beginning_of_year, '$lte' => Date.today.end_of_year}) + end + end diff --git a/app/models/page.rb b/app/models/page.rb index 4bcb49a6..970df4cf 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -1,10 +1,14 @@ class Page < Item + include Impressionist::Impressionable + + is_impressionable :counter_cache => { :column_name => :view_count } field :content field :app_frontend_url field :theme_id, :type => BSON::ObjectId, :default => nil field :category field :tag + field :view_count, :type => Integer, :default => 0 belongs_to :design belongs_to :module_app diff --git a/app/views/admin/dashboards/index.html.erb b/app/views/admin/dashboards/index.html.erb index ee0bc920..e81287ac 100644 --- a/app/views/admin/dashboards/index.html.erb +++ b/app/views/admin/dashboards/index.html.erb @@ -380,49 +380,7 @@
-
-

<%= t(:traffic) %>

-
-

<%= t(:total_visitors) %>438,913

- - - - - - - -
<%= t(:item) %><%= t(:data) %>
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - -
Visitors Today2,304
Visitors This Month783
Visitor This Tear45
-
-
-
-
-
+

<%= t(:site_info) %>

@@ -481,6 +439,56 @@
--> + + +
+

<%= t(:traffic) %>

+
+

<%= t(:total_visitors) %><%= display_visitors %>

+ + + + + + + +
<%= t(:item) %><%= t(:data) %>
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
<%= t(:visitors_today) %><%= display_visitors_today %>
<%= t(:visitors_this_week) %><%= display_visitors_this_week %>
<%= t(:visitors_this_month) %><%= display_visitors_this_month %>
<%= t(:visitors_this_year) %><%= display_visitors_this_year %>
+
+
+
+
+
+

<%= t(:recent_update) %>

@@ -517,4 +525,43 @@
+ +
+

<%= t(:most_visited_page) %>

+
+ + + + + + + + +
<%= t(:title) %><%= t(:module) %><%= t(:hits) %>
+
+
+
+
+
+
+
+
+
+
+ + + <% @most_visited.each do |object| %> + + + + + + <% end %> + +
<%= link_to ((object[0].title[I18n.locale] rescue nil) || (object[0].page.i18n_variable[I18n.locale] rescue nil)), get_link_to_object(object[0]) %><%= link_to t("dashboard.#{object[0]._type.underscore}"), get_link(object[0]._type.underscore) %><%= object[1] %>
+
+
+
+
+
\ No newline at end of file diff --git a/config/initializers/impression.rb b/config/initializers/impression.rb new file mode 100644 index 00000000..4b57f34a --- /dev/null +++ b/config/initializers/impression.rb @@ -0,0 +1,6 @@ +# Use this hook to configure impressionist parameters +Impressionist.setup do |config| + # Define ORM. Could be :active_record (default) and :mongo_mapper + # config.orm = :active_record + config.orm = :mongoid +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 374b3b33..3e2ffe8e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -42,6 +42,7 @@ en: sure?: Are you sure? update: Update view: View + view_count: View count yes_: "Yes" all_content: All Content @@ -67,6 +68,11 @@ en: total_visitors: Total Visitors traffic: Traffic + visitors_today: Today's visitors + visitors_this_week: This week's visitors + visitors_this_month: This month's visitors + visitors_this_year: This year's visitors + admin: access: denied: diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml index 7c2ac289..9a1a9952 100644 --- a/config/locales/zh_tw.yml +++ b/config/locales/zh_tw.yml @@ -36,11 +36,12 @@ zh_tw: sure?: 您肯定嗎? update: 更新 view: 檢視 + view_count: 查看次數 yes_: "Yes" - all_content: 全部內容有: - all_file: 全部檔案有: - all_member: 成員總數: + all_content: 全部內容有 + all_file: 全部檔案有 + all_member: 成員總數 content: 內容 data: 數據 file: 檔案 @@ -58,9 +59,14 @@ zh_tw: site_name: 網站名稱 statistics: 統計 title: 標題 - total_visitors: 造訪次數: + total_visitors: 造訪次數 traffic: 流量 + visitors_today: 今日造訪 + visitors_this_week: 本星期造訪 + visitors_this_month: 本月造訪 + visitors_this_year: 今年造訪 + admin: access: denied: diff --git a/vendor/built_in_modules/announcement/app/controllers/panel/announcement/front_end/bulletins_controller.rb b/vendor/built_in_modules/announcement/app/controllers/panel/announcement/front_end/bulletins_controller.rb index 56cbb312..f21c1a38 100644 --- a/vendor/built_in_modules/announcement/app/controllers/panel/announcement/front_end/bulletins_controller.rb +++ b/vendor/built_in_modules/announcement/app/controllers/panel/announcement/front_end/bulletins_controller.rb @@ -29,6 +29,7 @@ class Panel::Announcement::FrontEnd::BulletinsController < OrbitWidgetController preview_content else @bulletin = Bulletin.can_display.where(_id: params[:id]).first + impressionist(@bulletin) get_categorys end diff --git a/vendor/built_in_modules/announcement/app/models/bulletin.rb b/vendor/built_in_modules/announcement/app/models/bulletin.rb index 43f29344..8098f9a7 100644 --- a/vendor/built_in_modules/announcement/app/models/bulletin.rb +++ b/vendor/built_in_modules/announcement/app/models/bulletin.rb @@ -4,6 +4,9 @@ class Bulletin include Mongoid::Document include Mongoid::Timestamps include Mongoid::MultiParameterAttributes + include Impressionist::Impressionable + + is_impressionable :counter_cache => { :column_name => :view_count } has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy @@ -23,6 +26,7 @@ class Bulletin field :is_pending, :type => Boolean, :default => true field :is_rejected, :type => Boolean, :default => false + field :view_count, :type => Integer, :default => 0 field :not_checked_reason diff --git a/vendor/built_in_modules/announcement/app/views/panel/announcement/front_end/bulletins/show.html.erb b/vendor/built_in_modules/announcement/app/views/panel/announcement/front_end/bulletins/show.html.erb index eabacca9..37373ae9 100644 --- a/vendor/built_in_modules/announcement/app/views/panel/announcement/front_end/bulletins/show.html.erb +++ b/vendor/built_in_modules/announcement/app/views/panel/announcement/front_end/bulletins/show.html.erb @@ -5,6 +5,7 @@ <%= @bulletin.postdate %>  |  <%= User.find(@bulletin.create_user_id).sub_roles.collect{|t| t.key}.join(" ") rescue '' %> + <%= dislpay_view_count(@bulletin) %>
diff --git a/vendor/built_in_modules/news/app/controllers/panel/news/front_end/news_bulletins_controller.rb b/vendor/built_in_modules/news/app/controllers/panel/news/front_end/news_bulletins_controller.rb index 57ff9e0a..50b9c266 100644 --- a/vendor/built_in_modules/news/app/controllers/panel/news/front_end/news_bulletins_controller.rb +++ b/vendor/built_in_modules/news/app/controllers/panel/news/front_end/news_bulletins_controller.rb @@ -30,6 +30,7 @@ class Panel::News::FrontEnd::NewsBulletinsController < OrbitWidgetController preview_content else @news_bulletin = NewsBulletin.can_display.where(_id: params[:id]).first + impressionist(@news_bulletin) get_categorys end end diff --git a/vendor/built_in_modules/news/app/models/news_bulletin.rb b/vendor/built_in_modules/news/app/models/news_bulletin.rb index 538180dc..e6e7d9fc 100644 --- a/vendor/built_in_modules/news/app/models/news_bulletin.rb +++ b/vendor/built_in_modules/news/app/models/news_bulletin.rb @@ -3,7 +3,10 @@ class NewsBulletin include Mongoid::Document include Mongoid::Timestamps - include Mongoid::MultiParameterAttributes + include Mongoid::MultiParameterAttributes + include Impressionist::Impressionable + + is_impressionable :counter_cache => { :column_name => :view_count } has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy @@ -23,6 +26,7 @@ class NewsBulletin field :is_pending, :type => Boolean, :default => true field :is_rejected, :type => Boolean, :default => false + field :view_count, :type => Integer, :default => 0 field :not_checked_reason diff --git a/vendor/built_in_modules/news/app/views/panel/news/front_end/news_bulletins/show.html.erb b/vendor/built_in_modules/news/app/views/panel/news/front_end/news_bulletins/show.html.erb index cc63cd86..8959c6ae 100644 --- a/vendor/built_in_modules/news/app/views/panel/news/front_end/news_bulletins/show.html.erb +++ b/vendor/built_in_modules/news/app/views/panel/news/front_end/news_bulletins/show.html.erb @@ -5,6 +5,7 @@ <%= @news_bulletin.postdate %>  |  <%= @news_bulletin.unit_list_for_anc.title[I18n.locale] rescue '' %> + <%= dislpay_view_count(@news_bulletin) %>
diff --git a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb index b8842169..e1e98c76 100644 --- a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb +++ b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb @@ -6,10 +6,10 @@ class Panel::PageContent::FrontEnd::PageContextsController < OrbitWidgetControll end def index - - # @page_context = PageContext.where("page_id" => params[:page_id], :archived => false) - @page_context = PageContext.first(conditions: { page_id: params[:page_id], :archived => false }) - + # @page_context = PageContext.where("page_id" => params[:page_id], :archived => false) + @page_context = PageContext.first(conditions: { page_id: params[:page_id], :archived => false }) + impressionist(@page_context) + respond_to do |format| format.html # index.html.erb format.xml { render :xml => @page_contexts } diff --git a/vendor/built_in_modules/page_content/app/models/page_context.rb b/vendor/built_in_modules/page_content/app/models/page_context.rb index dfbfd4a9..be60db27 100644 --- a/vendor/built_in_modules/page_content/app/models/page_context.rb +++ b/vendor/built_in_modules/page_content/app/models/page_context.rb @@ -4,15 +4,17 @@ class PageContext include Mongoid::Document include Mongoid::Timestamps include Mongoid::MultiParameterAttributes - + include Impressionist::Impressionable include OrbitCoreLib::ObjectAuthable - + is_impressionable :counter_cache => { :column_name => :view_count } + has_one :context, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy field :create_user_id field :update_user_id field :version, :type => Integer , :default => 0 + field :view_count, :type => Integer, :default => 0 field :archived, :type => Boolean, :default => false # field :current, :type => Boolean, :default => false diff --git a/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb b/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb index 4f196d59..a27a56e7 100644 --- a/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb +++ b/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb @@ -6,3 +6,5 @@

<%= @page_context.page.i18n_variable[I18n.locale] rescue nil %>

<%= @page_context.context[I18n.locale].html_safe rescue nil %>
+ +
<%= dislpay_view_count(@page_context) %>
From 3462c0e3192f2d22ff01cf01e79623c5e442aa6c Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 21:31:54 +0800 Subject: [PATCH 07/11] Add each page name to page_context index --- .../page_contexts/_page_context.html.erb | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb index d2f5cfa1..036cb6e0 100644 --- a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb +++ b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb @@ -1,23 +1,23 @@ - - - - <%= page_context.page.path %> -
- -
- - - <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%> - <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %> - <%end -%> - - <%= page_context.updated_at.strftime("%Y-%m-%d %H:%I:%S") %> - <%= User.find(page_context.create_user_id).name %> - \ No newline at end of file + + + <%= page_context.page.path %>
+ <%= page_context.page.i18n_variable[I18n.locale] %> +
+ +
+ + + <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%> + <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %> + <%end -%> + + <%= page_context.updated_at.strftime("%Y-%m-%d %H:%I:%S") %> + <%= User.find(page_context.create_user_id).name %> + \ No newline at end of file From a8c1cbdbba8537de09483be41c9e3d5d1f221f19 Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 22:42:49 +0800 Subject: [PATCH 08/11] Add vendor/impressionist --- vendor/impressionist/.gitignore | 10 + vendor/impressionist/.rspec | 1 + vendor/impressionist/.travis.yml | 11 + vendor/impressionist/CHANGELOG.rdoc | 19 + vendor/impressionist/Gemfile | 13 + vendor/impressionist/LICENSE.txt | 20 + vendor/impressionist/README.md | 198 + vendor/impressionist/Rakefile | 21 + .../controllers/impressionist_controller.rb | 103 + vendor/impressionist/app/models/impression.rb | 3 + .../app/models/impressionist/bots.rb | 1468 ++++ .../models/impressionist/impressionable.rb | 64 + vendor/impressionist/impressionist.gemspec | 27 + .../active_record/impressionist_generator.rb | 21 + .../templates/create_impressions_table.rb | 30 + .../lib/generators/impressionist_generator.rb | 12 + .../mongo_mapper/impressionist_generator.rb | 8 + .../mongoid/impressionist_generator.rb | 8 + .../lib/generators/templates/impression.rb | 5 + vendor/impressionist/lib/impressionist.rb | 12 + .../impressionist/lib/impressionist/bots.rb | 18 + .../impressionist/lib/impressionist/engine.rb | 30 + .../models/active_record/impression.rb | 18 + .../impressionist/impressionable.rb | 12 + .../models/mongo_mapper/impression.rb | 16 + .../impressionist/impressionable.rb | 12 + .../models/mongoid/impression.rb | 37 + .../mongoid/impressionist/impressionable.rb | 12 + .../lib/impressionist/version.rb | 3 + vendor/impressionist/logo.png | Bin 0 -> 63294 bytes vendor/impressionist/test_app/.gitignore | 17 + vendor/impressionist/test_app/.rspec | 1 + vendor/impressionist/test_app/Gemfile | 59 + vendor/impressionist/test_app/README | 256 + vendor/impressionist/test_app/README.rdoc | 261 + vendor/impressionist/test_app/Rakefile | 7 + .../test_app/app/assets/images/rails.png | Bin 0 -> 6646 bytes .../app/assets/javascripts/application.js | 15 + .../app/assets/stylesheets/application.css | 13 + .../app/controllers/application_controller.rb | 8 + .../app/controllers/articles_controller.rb | 18 + .../app/controllers/dummy_controller.rb | 6 + .../app/controllers/posts_controller.rb | 23 + .../app/controllers/widgets_controller.rb | 12 + .../app/helpers/application_helper.rb | 2 + .../test_app/app/mailers/.gitkeep | 0 .../test_app/app/models/.gitkeep | 0 .../test_app/app/models/article.rb | 3 + .../test_app/app/models/dummy.rb | 7 + .../impressionist/test_app/app/models/post.rb | 3 + .../impressionist/test_app/app/models/user.rb | 3 + .../test_app/app/models/widget.rb | 3 + .../app/views/articles/index.html.erb | 1 + .../test_app/app/views/articles/show.html.erb | 1 + .../app/views/layouts/application.html.erb | 14 + .../test_app/app/views/posts/edit.html.erb | 0 .../test_app/app/views/posts/index.html.erb | 0 .../test_app/app/views/posts/show.html.erb | 0 .../test_app/app/views/widgets/index.html.erb | 0 .../test_app/app/views/widgets/new.html.erb | 0 .../test_app/app/views/widgets/show.html.erb | 0 vendor/impressionist/test_app/config.ru | 4 + .../test_app/config/application.rb | 59 + vendor/impressionist/test_app/config/boot.rb | 6 + .../test_app/config/cucumber.yml | 8 + .../test_app/config/database.yml | 30 + .../test_app/config/environment.rb | 5 + .../config/environments/development.rb | 37 + .../test_app/config/environments/pg_test.rb | 35 + .../config/environments/production.rb | 67 + .../test_app/config/environments/test.rb | 37 + .../initializers/backtrace_silencers.rb | 7 + .../config/initializers/impression.rb | 5 + .../config/initializers/inflections.rb | 15 + .../config/initializers/mime_types.rb | 5 + .../config/initializers/secret_token.rb | 7 + .../config/initializers/session_store.rb | 8 + .../config/initializers/wrap_parameters.rb | 14 + .../test_app/config/locales/en.yml | 5 + .../impressionist/test_app/config/routes.rb | 3 + .../migrate/20110201153144_create_articles.rb | 13 + .../db/migrate/20110210205028_create_posts.rb | 13 + .../migrate/20111127184039_create_widgets.rb | 15 + vendor/impressionist/test_app/db/seeds.rb | 7 + .../test_app/lib/assets/.gitkeep | 0 .../impressionist/test_app/lib/tasks/.gitkeep | 0 .../test_app/lib/tasks/cucumber.rake | 53 + vendor/impressionist/test_app/log/.gitkeep | 0 vendor/impressionist/test_app/public/404.html | 26 + vendor/impressionist/test_app/public/422.html | 26 + vendor/impressionist/test_app/public/500.html | 25 + .../impressionist/test_app/public/favicon.ico | 0 .../test_app/public/images/rails.png | Bin 0 -> 6646 bytes .../impressionist/test_app/public/index.html | 241 + .../public/javascripts/application.js | 2 + .../test_app/public/javascripts/controls.js | 965 +++ .../test_app/public/javascripts/dragdrop.js | 974 +++ .../test_app/public/javascripts/effects.js | 1123 +++ .../test_app/public/javascripts/prototype.js | 6001 +++++++++++++++++ .../test_app/public/javascripts/rails.js | 175 + .../impressionist/test_app/public/robots.txt | 5 + .../test_app/public/stylesheets/.gitkeep | 0 vendor/impressionist/test_app/script/cucumber | 10 + vendor/impressionist/test_app/script/rails | 6 + .../spec/controllers/controller_spec.rb | 125 + .../impressionist_uniqueness_spec.rb | 310 + .../test_app/spec/fixtures/articles.yml | 3 + .../test_app/spec/fixtures/impressions.yml | 43 + .../test_app/spec/fixtures/posts.yml | 3 + .../test_app/spec/fixtures/widgets.yml | 4 + .../spec/initializers/initializers_spec.rb | 18 + .../test_app/spec/models/bots_spec.rb | 27 + .../spec/models/counter_caching_spec.rb | 43 + .../test_app/spec/models/model_spec.rb | 94 + .../rails_generators/rails_generators_spec.rb | 23 + .../test_app/spec/spec_helper.rb | 36 + .../upgrade_migrations/version_0_3_0.rb | 27 + .../upgrade_migrations/version_0_4_0.rb | 9 + .../upgrade_migrations/version_1_1_2.rb | 9 + 119 files changed, 13756 insertions(+) create mode 100644 vendor/impressionist/.gitignore create mode 100644 vendor/impressionist/.rspec create mode 100644 vendor/impressionist/.travis.yml create mode 100644 vendor/impressionist/CHANGELOG.rdoc create mode 100644 vendor/impressionist/Gemfile create mode 100644 vendor/impressionist/LICENSE.txt create mode 100644 vendor/impressionist/README.md create mode 100644 vendor/impressionist/Rakefile create mode 100644 vendor/impressionist/app/controllers/impressionist_controller.rb create mode 100644 vendor/impressionist/app/models/impression.rb create mode 100644 vendor/impressionist/app/models/impressionist/bots.rb create mode 100644 vendor/impressionist/app/models/impressionist/impressionable.rb create mode 100644 vendor/impressionist/impressionist.gemspec create mode 100644 vendor/impressionist/lib/generators/active_record/impressionist_generator.rb create mode 100644 vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb create mode 100644 vendor/impressionist/lib/generators/impressionist_generator.rb create mode 100644 vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb create mode 100644 vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb create mode 100644 vendor/impressionist/lib/generators/templates/impression.rb create mode 100644 vendor/impressionist/lib/impressionist.rb create mode 100644 vendor/impressionist/lib/impressionist/bots.rb create mode 100644 vendor/impressionist/lib/impressionist/engine.rb create mode 100644 vendor/impressionist/lib/impressionist/models/active_record/impression.rb create mode 100644 vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb create mode 100644 vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb create mode 100644 vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb create mode 100644 vendor/impressionist/lib/impressionist/models/mongoid/impression.rb create mode 100644 vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb create mode 100644 vendor/impressionist/lib/impressionist/version.rb create mode 100644 vendor/impressionist/logo.png create mode 100644 vendor/impressionist/test_app/.gitignore create mode 100644 vendor/impressionist/test_app/.rspec create mode 100644 vendor/impressionist/test_app/Gemfile create mode 100644 vendor/impressionist/test_app/README create mode 100644 vendor/impressionist/test_app/README.rdoc create mode 100644 vendor/impressionist/test_app/Rakefile create mode 100644 vendor/impressionist/test_app/app/assets/images/rails.png create mode 100644 vendor/impressionist/test_app/app/assets/javascripts/application.js create mode 100644 vendor/impressionist/test_app/app/assets/stylesheets/application.css create mode 100644 vendor/impressionist/test_app/app/controllers/application_controller.rb create mode 100644 vendor/impressionist/test_app/app/controllers/articles_controller.rb create mode 100644 vendor/impressionist/test_app/app/controllers/dummy_controller.rb create mode 100644 vendor/impressionist/test_app/app/controllers/posts_controller.rb create mode 100644 vendor/impressionist/test_app/app/controllers/widgets_controller.rb create mode 100644 vendor/impressionist/test_app/app/helpers/application_helper.rb create mode 100644 vendor/impressionist/test_app/app/mailers/.gitkeep create mode 100644 vendor/impressionist/test_app/app/models/.gitkeep create mode 100644 vendor/impressionist/test_app/app/models/article.rb create mode 100644 vendor/impressionist/test_app/app/models/dummy.rb create mode 100644 vendor/impressionist/test_app/app/models/post.rb create mode 100644 vendor/impressionist/test_app/app/models/user.rb create mode 100644 vendor/impressionist/test_app/app/models/widget.rb create mode 100644 vendor/impressionist/test_app/app/views/articles/index.html.erb create mode 100644 vendor/impressionist/test_app/app/views/articles/show.html.erb create mode 100644 vendor/impressionist/test_app/app/views/layouts/application.html.erb create mode 100644 vendor/impressionist/test_app/app/views/posts/edit.html.erb create mode 100644 vendor/impressionist/test_app/app/views/posts/index.html.erb create mode 100644 vendor/impressionist/test_app/app/views/posts/show.html.erb create mode 100644 vendor/impressionist/test_app/app/views/widgets/index.html.erb create mode 100644 vendor/impressionist/test_app/app/views/widgets/new.html.erb create mode 100644 vendor/impressionist/test_app/app/views/widgets/show.html.erb create mode 100644 vendor/impressionist/test_app/config.ru create mode 100644 vendor/impressionist/test_app/config/application.rb create mode 100644 vendor/impressionist/test_app/config/boot.rb create mode 100644 vendor/impressionist/test_app/config/cucumber.yml create mode 100644 vendor/impressionist/test_app/config/database.yml create mode 100644 vendor/impressionist/test_app/config/environment.rb create mode 100644 vendor/impressionist/test_app/config/environments/development.rb create mode 100644 vendor/impressionist/test_app/config/environments/pg_test.rb create mode 100644 vendor/impressionist/test_app/config/environments/production.rb create mode 100644 vendor/impressionist/test_app/config/environments/test.rb create mode 100644 vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb create mode 100644 vendor/impressionist/test_app/config/initializers/impression.rb create mode 100644 vendor/impressionist/test_app/config/initializers/inflections.rb create mode 100644 vendor/impressionist/test_app/config/initializers/mime_types.rb create mode 100644 vendor/impressionist/test_app/config/initializers/secret_token.rb create mode 100644 vendor/impressionist/test_app/config/initializers/session_store.rb create mode 100644 vendor/impressionist/test_app/config/initializers/wrap_parameters.rb create mode 100644 vendor/impressionist/test_app/config/locales/en.yml create mode 100644 vendor/impressionist/test_app/config/routes.rb create mode 100644 vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb create mode 100644 vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb create mode 100644 vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb create mode 100644 vendor/impressionist/test_app/db/seeds.rb create mode 100644 vendor/impressionist/test_app/lib/assets/.gitkeep create mode 100644 vendor/impressionist/test_app/lib/tasks/.gitkeep create mode 100644 vendor/impressionist/test_app/lib/tasks/cucumber.rake create mode 100644 vendor/impressionist/test_app/log/.gitkeep create mode 100644 vendor/impressionist/test_app/public/404.html create mode 100644 vendor/impressionist/test_app/public/422.html create mode 100644 vendor/impressionist/test_app/public/500.html create mode 100644 vendor/impressionist/test_app/public/favicon.ico create mode 100644 vendor/impressionist/test_app/public/images/rails.png create mode 100644 vendor/impressionist/test_app/public/index.html create mode 100644 vendor/impressionist/test_app/public/javascripts/application.js create mode 100644 vendor/impressionist/test_app/public/javascripts/controls.js create mode 100644 vendor/impressionist/test_app/public/javascripts/dragdrop.js create mode 100644 vendor/impressionist/test_app/public/javascripts/effects.js create mode 100644 vendor/impressionist/test_app/public/javascripts/prototype.js create mode 100644 vendor/impressionist/test_app/public/javascripts/rails.js create mode 100644 vendor/impressionist/test_app/public/robots.txt create mode 100644 vendor/impressionist/test_app/public/stylesheets/.gitkeep create mode 100755 vendor/impressionist/test_app/script/cucumber create mode 100755 vendor/impressionist/test_app/script/rails create mode 100644 vendor/impressionist/test_app/spec/controllers/controller_spec.rb create mode 100644 vendor/impressionist/test_app/spec/controllers/impressionist_uniqueness_spec.rb create mode 100644 vendor/impressionist/test_app/spec/fixtures/articles.yml create mode 100644 vendor/impressionist/test_app/spec/fixtures/impressions.yml create mode 100644 vendor/impressionist/test_app/spec/fixtures/posts.yml create mode 100644 vendor/impressionist/test_app/spec/fixtures/widgets.yml create mode 100644 vendor/impressionist/test_app/spec/initializers/initializers_spec.rb create mode 100644 vendor/impressionist/test_app/spec/models/bots_spec.rb create mode 100644 vendor/impressionist/test_app/spec/models/counter_caching_spec.rb create mode 100644 vendor/impressionist/test_app/spec/models/model_spec.rb create mode 100644 vendor/impressionist/test_app/spec/rails_generators/rails_generators_spec.rb create mode 100644 vendor/impressionist/test_app/spec/spec_helper.rb create mode 100644 vendor/impressionist/upgrade_migrations/version_0_3_0.rb create mode 100644 vendor/impressionist/upgrade_migrations/version_0_4_0.rb create mode 100644 vendor/impressionist/upgrade_migrations/version_1_1_2.rb diff --git a/vendor/impressionist/.gitignore b/vendor/impressionist/.gitignore new file mode 100644 index 00000000..685130c7 --- /dev/null +++ b/vendor/impressionist/.gitignore @@ -0,0 +1,10 @@ +*~ +/coverage +/pkg +/rdoc +/test_app/db/schema.rb +/test_app/db/migrate/*_create_impressions_table.rb +/test_app/doc +/test_app/test +/test_app/vendor +Gemfile.lock diff --git a/vendor/impressionist/.rspec b/vendor/impressionist/.rspec new file mode 100644 index 00000000..4e1e0d2f --- /dev/null +++ b/vendor/impressionist/.rspec @@ -0,0 +1 @@ +--color diff --git a/vendor/impressionist/.travis.yml b/vendor/impressionist/.travis.yml new file mode 100644 index 00000000..b7ae9000 --- /dev/null +++ b/vendor/impressionist/.travis.yml @@ -0,0 +1,11 @@ +before_install: gem install bundler +before_script: "cd test_app && bundle install && ./script/rails generate impressionist && bundle exec rake db:migrate && cd .." +language: ruby +rvm: + - rbx-18mode + - rbx-19mode + - jruby-18mode + - 1.8.7 + - 1.9.2 + - 1.9.3 + - ruby-head diff --git a/vendor/impressionist/CHANGELOG.rdoc b/vendor/impressionist/CHANGELOG.rdoc new file mode 100644 index 00000000..c3378b22 --- /dev/null +++ b/vendor/impressionist/CHANGELOG.rdoc @@ -0,0 +1,19 @@ +== 0.4.0 (2011-06-03) +* Fix postgres bug +* New impression count method that accepts options for filter, start_date and end_date +* Add referrer to Impression model. YOU MUST RUN THE UPGRADE MIGRATION IF YOU ARE UPGRADING FROM 0.3.0 +* UPGRADE MIGRATION = impressionist/upgrade_migrations/version_0_4_0.rb +* NOTE IF YOU ARE UPGRADING FROM 0.2.5 OR BELOW, YOU MUST RUN BOTH UPGRADE MIGRATIONS + +== 0.3.0 (2011-03-06) +* added session_hash to impression model +* migration template updated to add session_hash +* new count instance method for impressionable model - unique_impression_count_session +* NOTE: if you are upgrading from 0.2.5, then run the migration in the 'upgrade_migrations' dir + +== 0.2.5 (2011-02-17) +* New model method - @widget.unique_impression_count_ip - This gives you unique impression account filtered by IP (and in turn request_hash since they have same IPs) +* @widget.unique_impression_count now uses request_hash. This was incorrectly stated in the README, since it was using ip_address. The README is correct as a result of the method change. + +== 0.2.4 (2011-02-17) +* Fix issue #1 - action_name and controller_name were not being logged for impressionist method inside action diff --git a/vendor/impressionist/Gemfile b/vendor/impressionist/Gemfile new file mode 100644 index 00000000..e6cc394d --- /dev/null +++ b/vendor/impressionist/Gemfile @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' + gem 'jruby-openssl' +end + +platforms :ruby, :mswin, :mingw do + # gem 'sqlite3' +end + +gemspec diff --git a/vendor/impressionist/LICENSE.txt b/vendor/impressionist/LICENSE.txt new file mode 100644 index 00000000..67e3f5c6 --- /dev/null +++ b/vendor/impressionist/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2011 cowboycoded + +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. diff --git a/vendor/impressionist/README.md b/vendor/impressionist/README.md new file mode 100644 index 00000000..40774e74 --- /dev/null +++ b/vendor/impressionist/README.md @@ -0,0 +1,198 @@ +![Impressionist Logo](https://github.com/charlotte-ruby/impressionist/raw/master/logo.png) + +[![Build Status](https://secure.travis-ci.org/charlotte-ruby/impressionist.png?branch=master)](http://travis-ci.org/charlotte-ruby/impressionist) + +impressionist +============= + +A lightweight plugin that logs impressions per action or manually per model + +-------------------------------------------------------------------------------- + +What does this thing do? +------------------------ +Logs an impression... and I use that term loosely. It can log page impressions +(technically action impressions), but it is not limited to that. You can log +impressions multiple times per request. And you can also attach it to a model. +The goal of this project is to provide customizable stats that are immediately +accessible in your application as opposed to using Google Analytics and pulling +data using their API. You can attach custom messages to impressions. No +reporting yet.. this thingy just creates the data. + +What about bots? +---------------- +They are ignored. 1200 known bots have been added to the ignore list as of +February 1, 2011. Impressionist uses this list: +http://www.user-agents.org/allagents.xml + +Installation +------------ +Add it to your Gemfile + + gem 'impressionist' + +Install with Bundler + + bundle install + +Generate the impressions table migration + + rails g impressionist + +Run the migration + + rake db:migrate + +The following fields are provided in the migration: + + t.string "impressionable_type" # model type: Widget + t.integer "impressionable_id" # model instance ID: @widget.id + t.integer "user_id" # automatically logs @current_user.id + t.string "controller_name" # logs the controller name + t.string "action_name" # logs the action_name + t.string "view_name" # TODO: log individual views (as well as partials and nested partials) + t.string "request_hash" # unique ID per request, in case you want to log multiple impressions and group them + t.string "session_hash" # logs the rails session + t.string "ip_address" # request.remote_ip + t.string "referrer" # request.referer + t.string "message" # custom message you can add + t.datetime "created_at" # I am not sure what this is.... Any clue? + t.datetime "updated_at" # never seen this one before either.... Your guess is as good as mine?? + +Usage +----- + +1. Log all actions in a controller + + WidgetsController < ApplicationController + impressionist + end + +2. Specify actions you want logged in a controller + + WidgetsController < ApplicationController + impressionist :actions=>[:show,:index] + end + +3. Make your models impressionable. This allows you to attach impressions to + an AR model instance. Impressionist will automatically log the Model name + (based on action_name) and the id (based on params[:id]), but in order to + get the count of impressions (example: @widget.impression_count), you will + need to make your model impressionalble + + class Widget < ActiveRecord::Base + is_impressionable + end + +4. Log an impression per model instance in your controller. Note that it is + not necessary to specify "impressionist" (usage #1) in the top of you + controller if you are using this method. If you add "impressionist" to the + top of your controller and also use this method in your action, it will + result in 2 impressions being logged (but associated with one request_hash) + + def show + @widget = Widget.find + impressionist(@widget,message:"wtf is a widget?") #message is optional + end + +5. Get unique impression count from a model. This groups impressions by + request_hash, so if you logged multiple impressions per request, it will + only count them one time. This unique impression count will not filter out + unique users, only unique requests + + @widget.impressionist_count + @widget.impressionist_count(:start_date=>"2011-01-01",:end_date=>"2011-01-05") + @widget.impressionist_count(:start_date=>"2011-01-01") #specify start date only, end date = now + +6. Get the unique impression count from a model filtered by IP address. This + in turn will give you impressions with unique request_hash, since rows with + the same request_hash will have the same IP address. + + @widget.impressionist_count(:filter=>:ip_address) + +7. Get the unique impression count from a model filtered by session hash. Same + as #6 regarding request hash. This may be more desirable than filtering by + IP address depending on your situation, since filtering by IP may ignore + visitors that use the same IP. The downside to this filtering is that a + user could clear session data in their browser and skew the results. + + @widget.impressionist_count(:filter=>:session_hash) + +8. Get total impression count. This may return more than 1 impression per http + request, depending on how you are logging impressions + + @widget.impressionist_count(:filter=>:all) + +Logging impressions for authenticated users happens automatically. If you have +a current_user helper or use @current_user in your before_filter to set your +authenticated user, current_user.id will be written to the user_id field in the +impressions table. + +Adding a counter cache +---------------------- +Impressionist makes it easy to add a `counter_cache` column to your model. The +most basic configuration looks like: + + is_impressionable :counter_cache => true + +This will automatically increment the `impressions_count` column in the +included model. Note: You'll need to add that column to your model. If you'd +like specific a different column name, you can: + + is_impressionable :counter_cache => { :column_name => :my_column } + +If you'd like to include only unique impressions in your count: + + is_impressionable :counter_cache => { :column_name => :my_column, :unique => true } + +What if I only want to record unique impressions? +------------------------------------------------- +Maybe you only care about unique impressions and would like to avoid +unnecessary database records. You can specify conditions for recording +impressions in your controller: + + # only record impression if the request has a unique combination of type, id, and session + impressionist :unique => [:impressionable_type, :impressionable_id, :session_hash] + + # only record impression if the request has a unique combination of controller, action, and session + impressionist :unique => [:controller_name, :action_name, :session_hash] + + # only record impression if session is unique + impressionist :unique => [:session_hash] + +Or you can use the `impressionist` method directly: + + impressionist(impressionable, "some message", :unique => [:session_hash]) + +Development Roadmap +------------------- +* Automatic impression logging in views. For example, log initial view, and + any partials called from initial view +* Customizable black list for user-agents or IP addresses. Impressions will be + ignored. Web admin as part of the Engine. +* Reporting engine +* AB testing integration + +Contributing to impressionist +----------------------------- +* Check out the latest master to make sure the feature hasn't been implemented + or the bug hasn't been fixed yet +* Check out the issue tracker to make sure someone already hasn't requested it + and/or contributed it +* Fork the project +* Start a feature/bugfix branch +* Commit and push until you are happy with your contribution +* Make sure to add rpsec tests for it. Patches or features without tests will + be ignored. Also, try to write better tests than I do ;-) +* If adding engine controller or view functionality, use HAML and Inherited + Resources. +* All testing is done inside a small Rails app (test_app). You will find specs + within this app. + +Contributors +------------ +* [johnmcaliley](https://github.com/johnmcaliley) +* [coryschires](https://github.com/coryschires) +* [georgmittendorfer](https://github.com/georgmittendorfer) + +Copyright (c) 2011 John McAliley. See LICENSE.txt for further details. diff --git a/vendor/impressionist/Rakefile b/vendor/impressionist/Rakefile new file mode 100644 index 00000000..d808f163 --- /dev/null +++ b/vendor/impressionist/Rakefile @@ -0,0 +1,21 @@ +require 'bundler/setup' +require 'rspec/core/rake_task' + +Bundler::GemHelper.install_tasks + +RSpec::Core::RakeTask.new do |task| + task.rspec_opts = "-I ./test_app/spec" + task.pattern = "./test_app/spec/**/*_spec.rb" +end + +task :test => :spec +task :default => :spec + +namespace :impressionist do + require File.dirname(__FILE__) + "/lib/impressionist/bots" + + desc "output the list of bots from http://www.user-agents.org/" + task :bots do + p Impressionist::Bots.consume + end +end diff --git a/vendor/impressionist/app/controllers/impressionist_controller.rb b/vendor/impressionist/app/controllers/impressionist_controller.rb new file mode 100644 index 00000000..05a39768 --- /dev/null +++ b/vendor/impressionist/app/controllers/impressionist_controller.rb @@ -0,0 +1,103 @@ +require 'digest/sha2' + +module ImpressionistController + module ClassMethods + def impressionist(opts={}) + before_filter { |c| c.impressionist_subapp_filter(opts[:actions], opts[:unique])} + end + end + + module InstanceMethods + def self.included(base) + base.before_filter :impressionist_app_filter + end + + def impressionist(obj,message=nil,opts={}) + unless bypass + if obj.respond_to?("impressionable?") + if unique_instance?(obj, opts[:unique]) + obj.impressions.create(associative_create_statement({:message => message})) + end + else + # we could create an impression anyway. for classes, too. why not? + raise "#{obj.class.to_s} is not impressionable!" + end + end + end + + def impressionist_app_filter + @impressionist_hash = Digest::SHA2.hexdigest(Time.now.to_f.to_s+rand(10000).to_s) + end + + def impressionist_subapp_filter(actions=nil,unique_opts=nil) + unless bypass + actions.collect!{|a|a.to_s} unless actions.blank? + if (actions.blank? || actions.include?(action_name)) && unique?(unique_opts) + Impression.create(direct_create_statement) + end + end + end + + private + + def bypass + Impressionist::Bots.bot?(request.user_agent) + end + + def unique_instance?(impressionable, unique_opts) + return unique_opts.blank? || !impressionable.impressions.where(unique_query(unique_opts)).exists? + end + + def unique?(unique_opts) + return unique_opts.blank? || !Impression.where(unique_query(unique_opts)).exists? + end + + # creates the query to check for uniqueness + def unique_query(unique_opts) + full_statement = direct_create_statement + # reduce the full statement to the params we need for the specified unique options + unique_opts.reduce({}) do |query, param| + query[param] = full_statement[param] + query + end + end + + # creates a statment hash that contains default values for creating an impression via an AR relation. + def associative_create_statement(query_params={}) + query_params.reverse_merge!( + :controller_name => controller_name, + :action_name => action_name, + :user_id => user_id, + :request_hash => @impressionist_hash, + :session_hash => session_hash, + :ip_address => request.remote_ip, + :referrer => request.referer + ) + end + + # creates a statment hash that contains default values for creating an impression. + def direct_create_statement(query_params={}) + query_params.reverse_merge!( + :impressionable_type => controller_name.singularize.camelize, + :impressionable_id=> params[:id] + ) + associative_create_statement(query_params) + end + + def session_hash + # # careful: request.session_options[:id] encoding in rspec test was ASCII-8BIT + # # that broke the database query for uniqueness. not sure if this is a testing only issue. + # str = request.session_options[:id] + # logger.debug "Encoding: #{str.encoding.inspect}" + # # request.session_options[:id].encode("ISO-8859-1") + request.session_options[:id] + end + + #use both @current_user and current_user helper + def user_id + user_id = @current_user ? @current_user.id : nil rescue nil + user_id = current_user ? current_user.id : nil rescue nil if user_id.blank? + user_id + end + end +end diff --git a/vendor/impressionist/app/models/impression.rb b/vendor/impressionist/app/models/impression.rb new file mode 100644 index 00000000..b4d81ecb --- /dev/null +++ b/vendor/impressionist/app/models/impression.rb @@ -0,0 +1,3 @@ +class Impression + belongs_to :impressionable, :polymorphic=>true +end diff --git a/vendor/impressionist/app/models/impressionist/bots.rb b/vendor/impressionist/app/models/impressionist/bots.rb new file mode 100644 index 00000000..aee7cf9c --- /dev/null +++ b/vendor/impressionist/app/models/impressionist/bots.rb @@ -0,0 +1,1468 @@ +module Impressionist + module Bots + + def self.bot?(user_agent = nil) + return false if user_agent.nil? + WILD_CARDS.any? { |wc| user_agent.downcase.include?(wc) } || LIST.include?(user_agent) + end + + WILD_CARDS = ["bot","yahoo","slurp","google","msn","crawler"] + + LIST = [" UnChaos From Chaos To Order Hybrid Web Search Engine.(vadim_gonchar@unchaos.com)", + " UnChaos Bot Hybrid Web Search Engine. (vadim_gonchar@unchaos.com)", + " UnChaosBot From Chaos To Order UnChaos Hybrid Web Search Engine at www.unchaos.com (info@unchaos.com)", + " http://www.sygol.com", + "*/Nutch-0.9-dev", + "+SitiDi.net/SitiDiBot/1.0 (+Have Good Day)", + "-DIE-KRAEHE- META-SEARCH-ENGINE/1.1 http://www.die-kraehe.de", + "192.comAgent", + "4anything.com LinkChecker v2.0", + "8484 Boston Project v 1.0", + ":robot/1.0 (linux) ( admin e-mail: undefined http://www.neofonie.de/loesungen/search/robot.html )", + "A-Online Search", + "A1 Sitemap Generator/1.0 (+http://www.micro-sys.dk/products/sitemap-generator/) miggibot/2006.01.24", + "aardvark-crawler", + "AbachoBOT", + "AbachoBOT (Mozilla compatible)", + "ABCdatos BotLink/5.xx.xxx#BBL", + "Aberja Checkomat", + "abot/0.1 (abot; http://www.abot.com; abot@abot.com)", + "About/0.1libwww-perl/5.47", + "Accelatech RSSCrawler/0.4", + "accoona", + "Accoona-AI-Agent/1.1.1 (crawler at accoona dot com)", + "Accoona-AI-Agent/1.1.2 (aicrawler at accoonabot dot com)", + "Ack (http://www.ackerm.com/)", + "AcoiRobot", + "Acoon Robot v1.50.001", + "Acoon Robot v1.52 (http://www.acoon.de)", + "Acoon-Robot 4.0.x.[xx] (http://www.acoon.de)", + "Acoon-Robot v3.xx (http://www.acoon.de and http://www.acoon.com)", + "Acorn/Nutch-0.9 (Non-Profit Search Engine; acorn.isara.org; acorn at isara dot org)", + "AESOP_com_SpiderMan", + "agadine/1.x.x (+http://www.agada.de)", + "Agent-SharewarePlazaFileCheckBot/2.0+(+http://www.SharewarePlaza.com)", + "AgentName/0.1 libwww-perl/5.48", + "AIBOT/2.1 By +(www.21seek.com A Real artificial intelligence search engine China)", + "aipbot/1.0 (aipbot; http://www.aipbot.com; aipbot@aipbot.com)", + "aipbot/2-beta (aipbot dev; http://aipbot.com; aipbot@aipbot.com)", + "Aladin/3.324", + "Aleksika Spider/1.0 (+http://www.aleksika.com/)", + "AlkalineBOT/1.3", + "AlkalineBOT/1.4 (1.4.0326.0 RTM)", + "Allesklar/0.1 libwww-perl/5.46", + "Allrati/1.1 (+)", + "AltaVista Intranet V2.0 AVS EVAL search@freeit.com", + "AltaVista Intranet V2.0 Compaq Altavista Eval sveand@altavista.net", + "AltaVista Intranet V2.0 evreka.com crawler@evreka.com", + "AltaVista V2.0B crawler@evreka.com", + "AmfibiBOT", + "Amfibibot/0.06 (Amfibi Web Search; http://www.amfibi.com; agent@amfibi.com)", + "Amfibibot/0.07 (Amfibi Robot; http://www.amfibi.com; agent@amfibi.com)", + "amibot", + "AnnoMille spider 0.1 alpha - http://www.annomille.it", + "AnswerBus (http://www.answerbus.com/)", + "antibot-V1.1.5/i586-linux-2.2", + "AnzwersCrawl/2.0 (anzwerscrawl@anzwers.com.au;Engine)", + "Apexoo Spider 1.x", + "Aport", + "appie 1.1 (www.walhello.com)", + "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)", + "ArachBot", + "Arachnoidea (arachnoidea@euroseek.com)", + "ArchitextSpider", + "archive.org_bot", + "Arikus_Spider", + "Arquivo-web-crawler (compatible; heritrix/1.12.1 +http://arquivo-web.fccn.pt)", + "ASAHA Search Engine Turkey V.001 (http://www.asaha.com/)", + "Asahina-Antenna/1.x", + "Asahina-Antenna/1.x (libhina.pl/x.x ; libtime.pl/x.x)", + "ask.24x.info", + "AskAboutOil/0.06-rcp (Nutch; http://www.nutch.org/docs/en/bot.html; nutch-agent@askaboutoil.com)", + "asked/Nutch-0.8 (web crawler; http://asked.jp; epicurus at gmail dot com)", + "ASPSeek/1.2.5", + "ASPseek/1.2.9d", + "ASPSeek/1.2.x", + "ASPSeek/1.2.xa", + "ASPseek/1.2.xx", + "ASPSeek/1.2.xxpre", + "ASSORT/0.10", + "asterias/2.0", + "AtlocalBot/1.1 +(http://www.atlocal.com/local-web-site-owner.html)", + "Atomic_Email_Hunter/4.0", + "Atomz/1.0", + "atSpider/1.0", + "Attentio/Nutch-0.9-dev (Attentio's beta blog crawler; www.attentio.com; info@attentio.com)", + "augurfind", + "augurnfind V-1.x", + "autoemailspider", + "autowebdir 1.1 (www.autowebdir.com)", + "AV Fetch 1.0", + "AVSearch-1.0(peter.turney@nrc.ca)", + "AVSearch-3.0(AltaVista/AVC)", + "axadine/ (Axadine Crawler; http://www.axada.de/; )", + "AxmoRobot - Crawling your site for better indexing on www.axmo.com search engine.", + "BabalooSpider/1.3 (BabalooSpider; http://www.babaloo.si; spider@babaloo.si)", + "BaboomBot/1.x.x (+http://www.baboom.us)", + "BaiduImagespider+(+http://www.baidu.jp/search/s308.html)", + "BaiDuSpider", + "Baiduspider+(+http://help.baidu.jp/system/05.html)", + "Baiduspider+(+http://www.baidu.com/search/spider.htm)", + "Baiduspider+(+http://www.baidu.com/search/spider_jp.html)", + "Balihoo/Nutch-1.0-dev (Crawler for Balihoo.com search engine - obeys robots.txt and robots meta tags ; http://balihoo.com/index.aspx; robot at balihoo dot com)", + "BarraHomeCrawler (albertof@barrahome.org)", + "bdcindexer_2.6.2 (research@bdc)", + "BDFetch", + "BDNcentral Crawler v2.3 [en] (http://www.bdncentral.com/robot.html) (X11; I; Linux 2.0.44 i686)", + "beautybot/1.0 (+http://www.uchoose.de/crawler/beautybot/)", + "BebopBot/2.5.1 ( crawler http://www.apassion4jazz.net/bebopbot.html )", + "BigCliqueBOT/1.03-dev (bigclicbot; http://www.bigclique.com; bot@bigclique.com)", + "BIGLOTRON (Beta 2;GNU/Linux)", + "Bigsearch.ca/Nutch-x.x-dev (Bigsearch.ca Internet Spider; http://www.bigsearch.ca/; info@enhancededge.com)", + "BilgiBetaBot/0.8-dev (bilgi.com (Beta) ; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)", + "BilgiBot/1.0(beta) (http://www.bilgi.com/; bilgi at bilgi dot com)", + "Bitacle bot/1.1", + "Bitacle Robot (V:1.0;) (http://www.bitacle.com)", + "BlackWidow", + "Blaiz-Bee/1.0 (+http://www.blaiz.net)", + "Blaiz-Bee/2.00.8222 (BE Internet Search Engine http://www.rawgrunt.com)", + "Blaiz-Bee/2.00.xxxx (+http://www.blaiz.net)", + "BlitzBOT@tricus.net", + "BlitzBOT@tricus.net (Mozilla compatible)", + "BlogBot/1.x", + "Bloglines Title Fetch/1.0 (http://www.bloglines.com)", + "Bloglines-Images/0.1 (http://www.bloglines.com)", + "Bloglines/3.1 (http://www.bloglines.com)", + "Blogpulse (info@blogpulse.com)", + "BlogPulseLive (support@blogpulse.com)", + "BlogSearch/1.x +http://www.icerocket.com/", + "blogsearchbot-pumpkin-3", + "BlogsNowBot, V 2.01 (+http://www.blogsnow.com/)", + "BlogVibeBot-v1.1 (spider@blogvibe.nl)", + "blogWatcher_Spider/0.1 (http://www.lr.pi.titech.ac.jp/blogWatcher/)", + "BlogzIce/1.0 (+http://icerocket.com; rhodes@icerocket.com)", + "BlogzIce/1.0 +http://www.icerocket.com/", + "BloobyBot", + "Bloodhound/Nutch-0.9 (Testing Crawler for Research - obeys robots.txt and robots meta tags ; http://balihoo.com/index.aspx; robot at balihoo dot com)", + "boitho.com-dc/0.xx (http://www.boitho.com/dcbot.html)", + "boitho.com-robot/1.x", + "boitho.com-robot/1.x (http://www.boitho.com/bot.html)", + "BPImageWalker/2.0 (www.bdbrandprotect.com)", + "BravoBrian SpiderEngine MarcoPolo", + "BruinBot (+http://webarchive.cs.ucla.edu/bruinbot.html) ", + "BSDSeek/1.0", + "BTbot/0.x (+http://www.btbot.com/btbot.html)", + "BuildCMS crawler (http://www.buildcms.com/crawler)", + "BullsEye", + "bumblebee@relevare.com", + "BurstFindCrawler/1.1 (crawler.burstfind.com; http://crawler.burstfind.com; crawler@burstfind.com)", + "Buscaplus Robi/1.0 (http://www.buscaplus.com/robi/)", + "bwh3_user_agent", + "Cabot/Nutch-0.9 (Amfibi's web-crawling robot; http://www.amfibi.com/cabot/; agent@amfibi.com)", + "Cabot/Nutch-1.0-dev (Amfibi's web-crawling robot; http://www.amfibi.com/cabot/; agent@amfibi.com)", + "carleson/1.0", + "Carnegie_Mellon_University_Research_WebBOT-->PLEASE READ-->http://www.andrew.cmu.edu/~brgordon/webbot/index.html http://www.andrew.cmu.edu/~brgordon/webbot/index.html", + "Carnegie_Mellon_University_WebCrawler http://www.andrew.cmu.edu/~brgordon/webbot/index.html", + "Catall Spider", + "CazoodleBot/CazoodleBot-0.1 (CazoodleBot Crawler; http://www.cazoodle.com/cazoodlebot; cazoodlebot@cazoodle.com)", + "CCBot/1.0 (+http://www.commoncrawl.org/bot.html)", + "ccubee/x.x", + "Ceramic Tile Installation Guide (http://www.floorstransformed.com)", + "cfetch/1.0", + "China Local Browse 2.6", + "ChristCRAWLER 2.0", + "CipinetBot (http://www.cipinet.com/bot.html)", + "ClariaBot/1.0", + "Claymont.com", + "CloakDetect/0.9 (+http://fulltext.seznam.cz/)", + "Clushbot/2.x (+http://www.clush.com/bot.html)", + "Clushbot/3.x-BinaryFury (+http://www.clush.com/bot.html)", + "Clushbot/3.xx-Ajax (+http://www.clush.com/bot.html)", + "Clushbot/3.xx-Hector (+http://www.clush.com/bot.html)", + "Clushbot/3.xx-Peleus (+http://www.clush.com/bot.html)", + "Cogentbot/1.X (+http://www.cogentsoftwaresolutions.com/bot.html)", + "combine/0.0", + "Combine/2.0 http://combine.it.lth.se/", + "Combine/3 http://combine.it.lth.se/", + "Combine/x.0", + "cometrics-bot, http://www.cometrics.de", + "Computer_and_Automation_Research_Institute_Crawler crawler@ilab.sztaki.hu", + "Comrite/0.7.1 (Nutch; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)", + "ContactBot/0.2", + "ContentSmartz", + "Convera Internet Spider V6.x", + "ConveraCrawler/0.2", + "ConveraCrawler/0.9d (+http://www.authoritativeweb.com/crawl)", + "ConveraMultiMediaCrawler/0.1 (+http://www.authoritativeweb.com/crawl)", + "CoolBot", + "cosmos/0.8_(robot@xyleme.com)", + "cosmos/0.9_(robot@xyleme.com)", + "CougarSearch/0.x (+http://www.cougarsearch.com/faq.shtml)", + "Covac TexAs Arachbot", + "Cowbot-0.1 (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)", + "Cowbot-0.1.x (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)", + "CrawlConvera0.1 (CrawlConvera@yahoo.com)", + "Crawler (cometsearch@cometsystems.com)", + "Crawler admin@crawler.de", + "Crawler V 0.2.x admin@crawler.de", + "crawler@alexa.com", + "CrawlerBoy Pinpoint.com", + "Crawllybot/0.1 (Crawllybot; +http://www.crawlly.com; crawler@crawlly.com)", + "CreativeCommons/0.06-dev (Nutch; http://www.nutch.org/docs/en/bot.html; nutch-agent@lists.sourceforge.net)", + "CrocCrawler vx.3 [en] (http://www.croccrawler.com) (X11; I; Linux 2.0.44 i686)", + "csci_b659/0.13", + "Cuasarbot/0.9b http://www.cuasar.com/spider_beta/ ", + "CurryGuide SiteScan 1.1", + "Custom Spider www.bisnisseek.com /1.0", + "CyberPatrol SiteCat Webbot (http://www.cyberpatrol.com/cyberpatrolcrawler.asp)", + "CydralSpider/1.x (Cydral Web Image Search; http://www.cydral.com)", + "CydralSpider/3.0 (Cydral Image Search; http://www.cydral.com)", + "DataCha0s/2.0", + "DataCha0s/2.0", + "DataFountains/DMOZ Downloader", + "DataFountains/Dmoz Downloader (http://ivia.ucr.edu/useragents.shtml)", + "DataFountains/DMOZ Feature Vector Corpus Creator (http://ivia.ucr.edu/useragents.shtml)", + "DataparkSearch/4.47 (+http://dataparksearch.org/bot)", + "DataparkSearch/4.xx (http://www.dataparksearch.org/)", + "DataSpear/1.0 (Spider; http://www.dataspear.com/spider.html; spider@dataspear.com)", + "DataSpearSpiderBot/0.2 (DataSpear Spider Bot; http://dssb.dataspear.com/bot.html; dssb@dataspear.com)", + "DatenBot( http://www.sicher-durchs-netz.de/bot.html)", + "DaviesBot/1.7 (www.wholeweb.net)", + "daypopbot/0.x", + "dbDig(http://www.prairielandconsulting.com)", + "DBrowse 1.4b", + "DBrowse 1.4d", + "dCSbot/1.1", + "de.searchengine.comBot 1.2 (http://de.searchengine.com/spider)", + "deepak-USC/ISI", + "DeepIndex", + "DeepIndex ( http://www.zetbot.com )", + "DeepIndex (www.en.deepindex.com)", + "DeepIndexer.ca", + "Demo Bot DOT 16b", + "Demo Bot Z 16b", + "Denmex websearch (http://search.denmex.com)", + "dev-spider2.searchpsider.com/1.3b", + "DiaGem/1.1 (http://www.skyrocket.gr.jp/diagem.html)", + "Diamond/x.0", + "DiamondBot", + "Digger/1.0 JDK/1.3.0rc3", + "DigOut4U", + "DIIbot/1.2", + "disco/Nutch-0.9 (experimental crawler; www.discoveryengine.com; disco-crawl@discoveryengine.com)", + "disco/Nutch-1.0-dev (experimental crawler; www.discoveryengine.com; disco-crawl@discoveryengine.com)", + "DittoSpyder", + "dloader(NaverRobot)/1.0", + "DoCoMo/1.0/Nxxxi/c10", + "DoCoMo/1.0/Nxxxi/c10/TB", + "DoCoMo/2.0 P900iV(c100;TB;W24H11) ", + "DoCoMo/2.0 SH902i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)", + "DoCoMo/2.0/SO502i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)", + "dodgebot/experimental", + "Download-Tipp Linkcheck (http://download-tipp.de/)", + "Drecombot/1.0 (http://career.drecom.jp/bot.html)", + "DSurf15a 01", + "DSurf15a 71", + "DSurf15a 81", + "DSurf15a VA", + "dtSearchSpider", + "DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)", + "Dumbot(version 0.1 beta - dumbfind.com)", + "Dumbot(version 0.1 beta - http://www.dumbfind.com/dumbot.html)", + "Dumbot(version 0.1 beta)", + "e-sense 1.0 ea(www.vigiltech.com/esensedisclaim.html)", + "e-SocietyRobot(http://www.yama.info.waseda.ac.jp/~yamana/es/)", + "eApolloBot/2.0 (compatible; heritrix/2.0.0-SNAPSHOT-20071024.170148 +http://www.eapollo-opto.com)", + "EARTHCOM.info/1.x [www.earthcom.info]", + "EARTHCOM.info/1.xbeta [www.earthcom.info]", + "EasyDL/3.xx", + "EasyDL/3.xx http://keywen.com/Encyclopedia/Bot", + "EBrowse 1.4b", + "EchO!/2.0", + "Educate Search VxB", + "egothor/3.0a (+http://www.xdefine.org/robot.html)", + "EgotoBot/4.8 (+http://www.egoto.com/about.htm)", + "ejupiter.com", + "elfbot/1.0 (+http://www.uchoose.de/crawler/elfbot/)", + "ELI/20070402:2.0 (DAUM RSS Robot, Daum Communications Corp.; +http://ws.daum.net/aboutkr.html)", + "EmailSiphon", + "EmailSpider", + "EmailWolf 1.00", + "EMPAS_ROBOT", + "EnaBot/1.x (http://www.enaball.com/crawler.html)", + "Enfish Tracker", + "Enterprise_Search/1.0", + "Enterprise_Search/1.0.xxx", + "Enterprise_Search/1.00.xxx;MSSQL (http://www.innerprise.net/es-spider.asp)", + "envolk/1.7 (+http://www.envolk.com/envolkspiderinfo.php)", + "envolk[ITS]spider/1.6(+http://www.envolk.com/envolkspider.html)", + "EroCrawler", + "ES.NET_Crawler/2.0 (http://search.innerprise.net/)", + "eseek-larbin_2.6.2 (crawler@exactseek.com)", + "ESISmartSpider", + "eStyleSearch 4 (compatible; MSIE 6.0; Windows NT 5.0)", + "ESurf15a 15", + "EuripBot/0.x (+http://www.eurip.com) GetFile", + "EuripBot/0.x (+http://www.eurip.com) GetRobots", + "EuripBot/0.x (+http://www.eurip.com) PreCheck", + "Eurobot/1.0 (http://www.ayell.eu)", + "EvaalSE - bot@evaal.com", + "eventax/1.3 (eventax; http://www.eventax.de/; info@eventax.de)", + "Everest-Vulcan Inc./0.1 (R&D project; host=e-1-24; http://everest.vulcan.com/crawlerhelp)", + "Everest-Vulcan Inc./0.1 (R&D project; http://everest.vulcan.com/crawlerhelp)", + "Exabot-Images/1.0", + "Exabot-Test/1.0", + "Exabot/2.0", + "Exabot/3.0", + "ExactSeek Crawler/0.1", + "exactseek-crawler-2.63 (crawler@exactseek.com)", + "exactseek-pagereaper-2.63 (crawler@exactseek.com)", + "exactseek.com", + "Exalead NG/MimeLive Client (convert/http/0.120)", + "Excalibur Internet Spider V6.5.4", + "Execrawl/1.0 (Execrawl; http://www.execrawl.com/; bot@execrawl.com)", + "exooba crawler/exooba crawler (crawler for exooba.com; http://www.exooba.com/; info at exooba dot com)", + "exooba/exooba crawler (exooba; exooba)", + "ExperimentalHenrytheMiragoRobot", + "ExtractorPro", + "EyeCatcher (Download-tipp.de)/1.0", + "Factbot 1.09 (see http://www.factbites.com/webmasters.php)", + "factbot : http://www.factbites.com/robots", + "Fast Crawler Gold Edition", + "FAST Enterprise Crawler 6 (Experimental)", + "FAST Enterprise Crawler 6 / Scirus scirus-crawler@fast.no; http://www.scirus.com/srsapp/contactus/", + "FAST Enterprise Crawler 6 used by Cobra Development (admin@fastsearch.com)", + "FAST Enterprise Crawler 6 used by Comperio AS (sts@comperio.no)", + "FAST Enterprise Crawler 6 used by FAST (FAST)", + "FAST Enterprise Crawler 6 used by Pages Jaunes (pvincent@pagesjaunes.fr)", + "FAST Enterprise Crawler 6 used by Sensis.com.au Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)", + "FAST Enterprise Crawler 6 used by Singapore Press Holdings (crawler@sphsearch.sg)", + "FAST Enterprise Crawler/6 (www.fastsearch.com)", + "FAST Enterprise Crawler/6.4 (helpdesk at fast.no)", + "FAST FirstPage retriever (compatible; MSIE 5.5; Mozilla/4.0)", + "FAST MetaWeb Crawler (helpdesk at fastsearch dot com)", + "Fast PartnerSite Crawler", + "FAST-WebCrawler/2.2.10 (Multimedia Search) (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)", + "FAST-WebCrawler/2.2.6 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)", + "FAST-WebCrawler/2.2.7 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)http://www.fast.no", + "FAST-WebCrawler/2.2.8 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)http://www.fast.no", + "FAST-WebCrawler/3.2 test", + "FAST-WebCrawler/3.3 (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)", + "FAST-WebCrawler/3.4/Nirvana (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)", + "FAST-WebCrawler/3.4/PartnerSite (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)", + "FAST-WebCrawler/3.5 (atw-crawler at fast dot no; http://fast.no/support.php?c=faqs/crawler)", + "FAST-WebCrawler/3.6 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)", + "FAST-WebCrawler/3.6/FirstPage (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)", + "FAST-WebCrawler/3.7 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)", + "FAST-WebCrawler/3.7/FirstPage (atw-crawler at fast dot no;http://fast.no/support/crawler.asp)", + "FAST-WebCrawler/3.8 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)", + "FAST-WebCrawler/3.8/Fresh (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)", + "FAST-WebCrawler/3.x Multimedia", + "FAST-WebCrawler/3.x Multimedia (mm dash crawler at fast dot no)", + "fastbot crawler beta 2.0 (+http://www.fastbot.de)", + "FastBug http://www.ay-up.com", + "FastCrawler 3.0.1 (crawler@1klik.dk)", + "FastSearch Web Crawler for Verizon SuperPages (kevin.watters@fastsearch.com)", + "Favcollector/2.0 (info@favcollector.com http://www.favcollector.com/)", + "favo.eu crawler/0.6 (http://www.favo.eu)", + "Faxobot/1.0", + "Feed Seeker Bot (RSS Feed Seeker http://www.MyNewFavoriteThing.com/fsb.php)", + "Feed24.com", + "FeedChecker/0.01", + "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html)", + "FeedHub FeedDiscovery/1.0 (http://www.feedhub.com)", + "FeedHub MetaDataFetcher/1.0 (http://www.feedhub.com)", + "Feedjit Favicon Crawler 1.0", + "Feedster Crawler/3.0; Feedster, Inc.", + "Felix - Mixcat Crawler (+http://mixcat.com)", + "FFC Trap Door Spider", + "Filtrbox/1.0", + "Findexa Crawler (http://www.findexa.no/gulesider/article26548.ece)", + "findlinks/x.xxx (+http://wortschatz.uni-leipzig.de/findlinks/) ", + "FineBot", + "Firefly/1.0", + "Firefly/1.0 (compatible; Mozilla 4.0; MSIE 5.5)", + "Firefox (kastaneta03@hotmail.com)", + "Firefox_1.0.6 (kasparek@naparek.cz)", + "FirstGov.gov Search - POC:firstgov.webmasters@gsa.gov", + "firstsbot", + "Flapbot/0.7.2 (Flaptor Crawler; http://www.flaptor.com; crawler at flaptor period com)", + "Flexum spider", + "Flexum/2.0", + "FlickBot 2.0 RPT-HTTPClient/0.3-3", + "flunky", + "FnooleBot/2.5.2 (+http://www.fnoole.com/addurl.html)", + "FocusedSampler/1.0", + "Folkd.com Spider/0.1 beta 1 (www.folkd.com)", + "Fooky.com/ScorpionBot/ScoutOut; http://www.fooky.com/scorpionbots", + "Francis/1.0 (francis@neomo.de http://www.neomo.de/)", + "Franklin Locator 1.8", + "FreeFind.com-SiteSearchEngine/1.0 (http://freefind.com; spiderinfo@freefind.com)", + "FreshNotes crawler< report problems to crawler-at-freshnotes-dot-com", + "FSurf15a 01", + "FTB-Bot http://www.findthebest.co.uk/", + "Full Web Bot 0416B", + "Full Web Bot 0516B", + "Full Web Bot 2816B", + "FuseBulb.Com", + "FyberSpider (+http://www.fybersearch.com/fyberspider.php)", + "GAIS Robot/1.0B2", + "Gaisbot/3.0 (indexer@gais.cs.ccu.edu.tw; http://gais.cs.ccu.edu.tw/robot.php)", + "Gaisbot/3.0+(robot06@gais.cs.ccu.edu.tw;+http://gais.cs.ccu.edu.tw/robot.php)", + "GalaxyBot/1.0 (http://www.galaxy.com/galaxybot.html)", + "Gallent Search Spider v1.4 Robot 2 (http://robot.GallentSearch.com)", + "gamekitbot/1.0 (+http://www.uchoose.de/crawler/gamekitbot/)", + "GammaSpider/1.0", + "gazz/x.x (gazz@nttrd.com)", + "generic_crawler/01.0217/", + "genieBot (http://64.5.245.11/faq/faq.html)", + "geniebot wgao@genieknows.com", + "GeonaBot 1.x; http://www.geona.com/", + "gigabaz/3.1x (baz@gigabaz.com; http://gigabaz.com/gigabaz/)", + "Gigabot/2.0 (gigablast.com)", + "Gigabot/2.0/gigablast.com/spider.html", + "Gigabot/2.0; http://www.gigablast.com/spider.html", + "Gigabot/2.0att", + "Gigabot/3.0 (http://www.gigablast.com/spider.html)", + "Gigabot/x.0", + "GigabotSiteSearch/2.0 (sitesearch.gigablast.com)", + "GNODSPIDER (www.gnod.net)", + "Goblin/0.9 (http://www.goguides.org/)", + "Goblin/0.9.x (http://www.goguides.org/goblin-info.html)", + "GoForIt.com", + "GOFORITBOT ( http://www.goforit.com/about/ )", + "gonzo1[P] +http://www.suchen.de/popups/faq.jsp", + "gonzo2[P] +http://www.suchen.de/faq.html", + "Goofer/0.2", + "Googlebot-Image/1.0", + "Googlebot-Image/1.0 ( http://www.googlebot.com/bot.html)", + "Googlebot/2.1 ( http://www.google.com/bot.html)", + "Googlebot/2.1 ( http://www.googlebot.com/bot.html)", + "Googlebot/Test ( http://www.googlebot.com/bot.html)", + "GrapeFX/0.3 libwww/5.4.0", + "great-plains-web-spider/flatlandbot (Flatland Industries Web Spider; http://www.flatlandindustries.com/flatlandbot.php; jason@flatlandindustries.com)", + "GrigorBot 0.8 (http://www.grigor.biz/bot.html)", + "Gromit/1.0", + "grub crawler(http://www.grub.org)", + "grub-client", + "gsa-crawler (Enterprise; GID-01422; jplastiras@google.com)", + "gsa-crawler (Enterprise; GID-01742;gsatesting@rediffmail.com)", + "gsa-crawler (Enterprise; GIX-02057; dm@enhesa.com)", + "gsa-crawler (Enterprise; GIX-03519; cknuetter@stubhub.com)", + "gsa-crawler (Enterprise; GIX-0xxxx; enterprise-training@google.com)", + "Guestbook Auto Submitter", + "Gulliver/1.3", + "Gulper Web Bot 0.2.4 (www.ecsl.cs.sunysb.edu/~maxim/cgi-bin/Link/GulperBot)", + "Gungho/0.08004 (http://code.google.com/p/gungho-crawler/wiki/Index)", + "GurujiBot/1.0 (+http://www.guruji.com/WebmasterFAQ.html)", + "GurujiImageBot/1.0 (+http://www.guruji.com/en/WebmasterFAQ.html)", + "HappyFunBot/1.1", + "Harvest-NG/1.0.2", + "Hatena Antenna/0.4 (http://a.hatena.ne.jp/help#robot)", + "Hatena Pagetitle Agent/1.0", + "Hatena RSS/0.3 (http://r.hatena.ne.jp)", + "hbtronix.spider.2 -- http://hbtronix.de/spider.php", + "HeinrichderMiragoRobot", + "HeinrichderMiragoRobot (http://www.miragorobot.com/scripts/deinfo.asp)", + "Helix/1.x ( http://www.sitesearch.ca/helix/)", + "HenriLeRobotMirago (http://www.miragorobot.com/scripts/frinfo.asp)", + "HenrytheMiragoRobot", + "HenryTheMiragoRobot (http://www.miragorobot.com/scripts/mrinfo.asp)", + "Hi! I'm CsCrawler my homepage: http://www.kde.cs.uni-kassel.de/lehre/ss2005/googlespam/crawler.html RPT-HTTPClient/0.3-3", + "Hippias/0.9 Beta", + "HitList", + "Hitwise Spider v1.0 http://www.hitwise.com", + "holmes/3.11 (http://morfeo.centrum.cz/bot)", + "holmes/3.9 (onet.pl)", + "holmes/3.xx (OnetSzukaj/5.0; +http://szukaj.onet.pl)", + "holmes/x.x", + "HolmesBot (http://holmes.ge)", + "HomePageSearch(hpsearch.uni-trier.de)", + "Homerbot: www.homerweb.com", + "Honda-Search/0.7.2 (Nutch; http://lucene.apache.org/nutch/bot.html; search@honda-search.com)", + "HooWWWer/2.1.3 (debugging run) (+http://cosco.hiit.fi/search/hoowwwer/ | mailto:crawler-infohiit.fi)", + "HooWWWer/2.1.x ( http://cosco.hiit.fi/search/hoowwwer/ | mailto:crawler-infohiit.fi)", + "HPL/Nutch-0.9 -", + "htdig/3.1.6 (http://computerorgs.com)", + "htdig/3.1.6 (unconfigured@htdig.searchengine.maintainer)", + "htdig/3.1.x (root@localhost)", + "http://Ask.24x.Info/ (http://narres.it/)", + "http://hilfe.acont.de/bot.html ACONTBOT", + "http://www.almaden.ibm.com/cs/crawler", + "http://www.almaden.ibm.com/cs/crawler [rc1.wf.ibm.com]", + "http://www.almaden.ibm.com/cs/crawler [wf216]", + "http://www.istarthere.com_spider@istarthere.com", + "http://www.monogol.de", + "http://www.trendtech.dk/spider.asp)", + "i1searchbot/2.0 (i1search web crawler; http://www.i1search.com; crawler@i1search.com)", + "IAArchiver-1.0", + "iaskspider2 (iask@staff.sina.com.cn)", + "ia_archiver", + "ia_archiver-web.archive.org", + "ia_archiver/1.6", + "ICC-Crawler(Mozilla-compatible; http://kc.nict.go.jp/icc/crawl.html; icc-crawl(at)ml(dot)nict(dot)go(dot)jp)", + "ICC-Crawler(Mozilla-compatible;http://kc.nict.go.jp/icc/crawl.html;icc-crawl-contact(at)ml(dot)nict(dot)go(dot)jp)", + "iCCrawler (http://www.iccenter.net)", + "ICCrawler - ICjobs (http://www.icjobs.de/bot.htm)", + "ichiro/x.0 (http://help.goo.ne.jp/door/crawler.html)", + "ichiro/x.0 (ichiro@nttr.co.jp)", + "IconSurf/2.0 favicon finder (see http://iconsurf.com/robot.html)", + "IconSurf/2.0 favicon monitor (see http://iconsurf.com/robot.html)", + "ICRA_label_spider/x.0", + "icsbot-0.1", + "ideare - SignSite/1.x", + "iFeed.jp/2.0 (www.psychedelix.com/agents/agents.rss; 0 subscribers)", + "igdeSpyder (compatible; igde.ru; +http://igde.ru/doc/tech.html)", + "IIITBOT/1.1 (Indian Language Web Search Engine; http://webkhoj.iiit.net; pvvpr at iiit dot ac dot in)", + "ilial/Nutch-0.9 (Ilial, Inc. is a Los Angeles based Internet startup company. For more information please visit http://www.ilial.com/crawler; http://www.ilial.com/crawler; crawl@ilial.com)", + "ilial/Nutch-0.9-dev", + "IlseBot/1.x", + "IlTrovatore-Setaccio ( http://www.iltrovatore.it)", + "Iltrovatore-Setaccio/0.3-dev (Indexing; http://www.iltrovatore.it/bot.html; info@iltrovatore.it)", + "IlTrovatore-Setaccio/1.2 ( http://www.iltrovatore.it/aiuto/faq.html)", + "Iltrovatore-Setaccio/1.2 (It-bot; http://www.iltrovatore.it/bot.html; info@iltrovatore.it)", + "iltrovatore-setaccio/1.2-dev (spidering; http://www.iltrovatore.it/aiuto/.....)", + "IlTrovatore/1.2 (IlTrovatore; http://www.iltrovatore.it/bot.html; bot@iltrovatore.it)", + "ImageWalker/2.0 (www.bdbrandprotect.com)", + "IncyWincy data gatherer(webmaster@loopimprovements.com", + "IncyWincy page crawler(webmaster@loopimprovements.com", + "IncyWincy(http://www.look.com)", + "IncyWincy(http://www.loopimprovements.com/robot.html)", + "IncyWincy/2.1(loopimprovements.com/robot.html)", + "IndexTheWeb.com Crawler7", + "Industry Program 1.0.x", + "Inet library", + "info@pubblisito.com- (http://www.pubblisito.com) il Sud dei Motori di Ricerca", + "InfoFly/1.0 (http://www.versions-project.org/)", + "INFOMINE/8.0 Adders", + "INFOMINE/8.0 RemoteServices", + "INFOMINE/8.0 VLCrawler (http://infomine.ucr.edu/useragents)", + "InfoNaviRobot(F107)", + "InfoSeek Sidewinder/0.9", + "InfoSeek Sidewinder/1.0A", + "InfoSeek Sidewinder/1.1A", + "Infoseek SideWinder/1.45 (Compatible; MSIE 10.0; UNIX)", + "Infoseek SideWinder/2.0B (Linux 2.4 i686)", + "INGRID/3.0 MT (webcrawler@NOSPAMexperimental.net; http://webmaster.ilse.nl/jsp/webmaster.jsp)", + "Inktomi Search", + "InnerpriseBot/1.0 (http://www.innerprise.com/)", + "Insitor.com search and find world wide!", + "Insitornaut", + "Internet Ninja x.0", + "InternetArchive/0.8-dev(Nutch;http://lucene.apache.org/nutch/bot.html;nutch-agent@lucene.apache", + "InternetSeer.com", + "IOI/2.0 (ISC Open Index crawler; http://index.isc.org/; bot@index.isc.org)", + "IPiumBot laurion(dot)com", + "IpselonBot/0.xx-beta (Ipselon; http://www.ipselon.com; ipselonbot@ipselon.com)", + "IRLbot/1.0 ( http://irl.cs.tamu.edu/crawler)", + "IRLbot/3.0 (compatible; MSIE 6.0; http://irl.cs.tamu.edu/crawler/)", + "ISC Systems iRc Search 2.1", + "IUPUI Research Bot v 1.9a", + "IWAgent/ 1.0 - www.brandprotect.com", + "Jabot/6.x (http://odin.ingrid.org/)", + "Jabot/7.x.x (http://odin.ingrid.org/)", + "Jack", + "Jambot/0.1.x (Jambot; http://www.jambot.com/blog; crawler@jambot.com)", + "Jambot/0.2.1 (Jambot; http://www.jambot.com/blog/static.php?page=webmaster-robot; crawler@jambot.com)", + "Jayde Crawler. http://www.jayde.com", + "Jetbot/1.0", + "JobSpider_BA/1.1", + "Jyxobot/x", + "k2spider", + "KAIST AITrc Crawler", + "KakleBot - www.kakle.com/0.1 (KakleBot - www.kakle.com; http:// www.kakle.com/bot.html; support@kakle.com)", + "kalooga/kalooga-4.0-dev-datahouse (Kalooga; http://www.kalooga.com; info@kalooga.com)", + "kalooga/KaloogaBot (Kalooga; http://www.kalooga.com/info.html?page=crawler; crawler@kalooga.com)", + "Kenjin Spider", + "Kevin http://dznet.com/kevin/", + "Kevin http://websitealert.net/kevin/", + "KE_1.0/2.0 libwww/5.2.8", + "KFSW-Bot (Version: 1.01 powered by KFSW www.kfsw.de)", + "kinja-imagebot (http://www.kinja.com/)", + "kinjabot (http://www.kinja.com)", + "KIT-Fireball/2.0", + "KIT-Fireball/2.0 (compatible; Mozilla 4.0; MSIE 5.5)", + "KnowItAll(knowitall@cs.washington.edu)", + "Knowledge.com/0.x", + "Krugle/Krugle,Nutch/0.8+ (Krugle web crawler; http://www.krugle.com/crawler/info.html; webcrawler@krugle.com)", + "KSbot/1.0 (KnowledgeStorm crawler; http://www.knowledgestorm.com/resources/content/crawler/index.html; crawleradmin@knowledgestorm.com)", + "kuloko-bot/0.x", + "kulokobot www.kuloko.com kuloko@backweave.com", + "kulturarw3/0.1", + "LapozzBot/1.4 ( http://robot.lapozz.com)", + "LapozzBot/1.5 (+http://robot.lapozz.hu)", + "larbin (samualt9@bigfoot.com)", + "LARBIN-EXPERIMENTAL (efp@gmx.net)", + "larbin_2.1.1 larbin2.1.1@somewhere.com", + "larbin_2.2.0 (crawl@compete.com)", + "larbin_2.2.1_de_Viennot (Laurent.Viennot@inria.fr)", + "larbin_2.2.2 (sugayama@lab7.kuis.kyoto-u.ac.jp)", + "larbin_2.2.2_guillaume (guillaume@liafa.jussieu.fr)", + "larbin_2.6.0 (larbin2.6.0@unspecified.mail)", + "larbin_2.6.1 (larbin2.6.1@unspecified.mail)", + "larbin_2.6.2 (hamasaki@grad.nii.ac.jp)", + "larbin_2.6.2 (larbin2.6.2@unspecified.mail)", + "larbin_2.6.2 (listonATccDOTgatechDOTedu)", + "larbin_2.6.2 (pimenas@systems.tuc.gr)", + "larbin_2.6.2 (tom@lemurconsulting.com)", + "larbin_2.6.2 (vitalbox1@hotmail.com)", + "larbin_2.6.3 (ltaa_web_crawler@groupes.epfl.ch)", + "larbin_2.6.3 (wgao@genieknows.com)", + "larbin_2.6.3_for_(http://cosco.hiit.fi/search/) tsilande@hiit.fi", + "larbin_2.6_basileocaml (basile.starynkevitch@cea.fr)", + "larbin_devel (http://pauillac.inria.fr/~ailleret/prog/larbin/)", + "lawinfo-crawler/Nutch-0.9-dev (Crawler for lawinfo.com pages; http://www.lawinfo.com; webmaster@lawinfo.com)", + "LECodeChecker/3.0 libgetdoc/1.0", + "LEIA/2.90", + "LEIA/3.01pr (LEIAcrawler; [SNIP])", + "LetsCrawl.com/1.0 +http://letscrawl.com/", + "LexiBot/1.00", + "Libby_1.1/libwww-perl/5.47", + "LibertyW (+http://www.lw01.com)", + "libWeb/clsHTTP -- hiongun@kt.co.kr", + "libwww-perl/5.41", + "libwww-perl/5.45", + "libwww-perl/5.48", + "libwww-perl/5.52 FP/2.1", + "libwww-perl/5.52 FP/4.0", + "libwww-perl/5.65", + "libwww-perl/5.800", + "libwww/5.3.2", + "LijitSpider/Nutch-0.9 (Reports crawler; http://www.lijit.com/; info(a)lijit(d)com)", + "Lincoln State Web Browser", + "linkbot", + "linknzbot", + "Links 2.0 (http://gossamer-threads.com/scripts/links/)", + "Links SQL (http://gossamer-threads.com/scripts/links-sql/)", + "LinkScan/11.0beta2 UnixShareware robot from Elsop.com (used by Indiafocus/Indiainfo)", + "LinkScan/9.0g Unix", + "LinkScan/x.x Unix", + "LiveTrans/Nutch-0.9 (maintainer: cobain at iis dot sinica dot edu dot tw; http://wkd.iis.sinica.edu.tw/LiveTrans/)", + "Llaut/1.0 (http://mnm.uib.es/~gallir/llaut/bot.html)", + "LMQueueBot/0.2", + "lmspider (lmspider@scansoft.com)", + "LNSpiderguy", + "LocalBot/1.0 ( http://www.localbot.co.uk/)", + "LocalcomBot/1.2.x ( http://www.local.com/bot.htm)", + "Lockstep Spider/1.0", + "Look.com", + "Lovel as 1.0 ( +http://www.everatom.com)", + "LTI/LemurProject Nutch Spider/Nutch-1.0-dev (lti crawler for CMU; http://www.lti.cs.cmu.edu; changkuk at cmu dot edu)", + "LTI/LemurProject Nutch Spider/Nutch-1.0-dev (Research spider using Nutch; http://www.lemurproject.org; mhoy@cs.cmu.edu)", + "lwp-trivial/1.32", + "lwp-trivial/1.34", + "lwp-trivial/1.34", + "LWP::Simple/5.22", + "LWP::Simple/5.36", + "LWP::Simple/5.48", + "LWP::Simple/5.50", + "LWP::Simple/5.51", + "LWP::Simple/5.53", + "LWP::Simple/5.63", + "LWP::Simple/5.803", + "Lycos_Spider_(modspider)", + "Lycos_Spider_(T-Rex)", + "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6c (human-guided@lerly.net)", + "Mac Finder 1.0.xx", + "Mackster( http://www.ukwizz.com )", + "Mahiti.Com/Mahiti Crawler-1.0 (Mahiti.Com; http://mahiti.com ; mahiti.com)", + "Mail.Ru/1.0", + "mailto:webcraft@bea.com", + "mammoth/1.0 ( http://www.sli-systems.com/)", + "MantraAgent", + "MapoftheInternet.com ( http://MapoftheInternet.com)", + "Mariner/5.1b [de] (Win95; I ;Kolibri gncwebbot)", + "Marketwave Hit List", + "Martini", + "MARTINI", + "Marvin v0.3", + "MaSagool/1.0 (MaSagool; http://sagool.jp/; info@sagool.jp)", + "MasterSeek", + "Mata Hari/2.00 ", + "Matrix S.p.A. - FAST Enterprise Crawler 6 (Unknown admin e-mail address)", + "maxomobot/dev-20051201 (maxomo; http://67.102.134.34:4047/MAXOMO/MAXOMObot.html; maxomobot@maxomo.com)", + "MDbot/1.0 (+http://www.megadownload.net/bot.html)", + "MediaCrawler-1.0 (Experimental)", + "Mediapartners-Google/2.1 ( http://www.googlebot.com/bot.html)", + "MediaSearch/0.1", + "MegaSheep v1.0 (www.searchuk.com internet sheep)", + "Megite2.0 (http://www.megite.com)", + "Mercator-1.x", + "Mercator-2.0", + "Mercator-Scrub-1.1", + "Metaeuro Web Crawler/0.2 (MetaEuro Web Search Clustering Engine; http://www.metaeuro.com; crawler at metaeuro dot com)", + "MetaGer-LinkChecker", + "MetagerBot/0.8-dev (MetagerBot; http://metager.de; )", + "MetaGer_PreChecker0.1", + "Metaspinner/0.01 (Metaspinner; http://www.meta-spinner.de/; support@meta-spinner.de/)", + "metatagsdir/0.7 (+http://metatagsdir.com/directory/)", + "MFC Foundation Class Library 4.0", + "MicroBaz", + "Microsoft Small Business Indexer", + "Microsoft URL Control - 6.00.8xxx", + "MicrosoftPrototypeCrawler (How's my crawling? mailto:newbiecrawler@hotmail.com)", + "Missauga Locate 1.0.0", + "Missigua Locator 1.9", + "Missouri College Browse", + "Misterbot-Nutch/0.7.1 (Misterbot-Nutch; http://www.misterbot.fr; admin@misterbot.fr)", + "Miva (AlgoFeedback@miva.com)", + "Mizzu Labs 2.2", + "MJ12bot/vx.x.x (http://majestic12.co.uk/bot.php?+)", + "MJ12bot/vx.x.x (http://www.majestic12.co.uk/projects/dsearch/mj12bot.php)", + "MJBot (SEO assessment)", + "MLBot (www.metadatalabs.com)", + "MnogoSearch/3.2.xx", + "Mo College 1.9", + "moget/x.x (moget@goo.ne.jp)", + "mogimogi/1.0", + "MojeekBot/0.x (archi; http://www.mojeek.com/bot.html)", + "Morris - Mixcat Crawler ( http://mixcat.com)", + "Mouse-House/7.4 (spider_monkey spider info at www.mobrien.com/sm.shtml)", + "mozDex/0.xx-dev (mozDex; http://www.mozdex.com/en/bot.html; spider@mozdex.com)", + "Mozilla (Mozilla@somewhere.com)", + "Mozilla 4.0(compatible; BotSeer/1.0; +http://botseer.ist.psu.edu)", + "Mozilla/2.0 (compatible; Ask Jeeves)", + "Mozilla/2.0 (compatible; Ask Jeeves/Teoma)", + "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; http://about.ask.com/en/docs/about/webmasters.shtml) ", + "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; http://sp.ask.com/docs/about/tech_crawling.html)", + "Mozilla/2.0 (compatible; EZResult -- Internet Search Engine)", + "Mozilla/2.0 (compatible; NEWT ActiveX; Win32)", + "Mozilla/2.0 (compatible; T-H-U-N-D-E-R-S-T-O-N-E)", + "Mozilla/3.0 (compatible; Fluffy the spider; http://www.searchhippo.com/; info@searchhippo.com)", + "Mozilla/3.0 (compatible; Indy Library)", + "Mozilla/3.0 (compatible; MuscatFerret/1.5.4; claude@euroferret.com)", + "Mozilla/3.0 (compatible; MuscatFerret/1.5; olly@muscat.co.uk)", + "Mozilla/3.0 (compatible; MuscatFerret/1.6.x; claude@euroferret.com)", + "Mozilla/3.0 (compatible; scan4mail (advanced version) http://www.peterspages.net/?scan4mail)", + "Mozilla/3.0 (compatible; ScollSpider; http://www.webwobot.com)", + "Mozilla/3.0 (compatible; Webinator-DEV01.home.iprospect.com/2.56)", + "Mozilla/3.0 (compatible; Webinator-indexer.cyberalert.com/2.56)", + "Mozilla/3.0 (INGRID/3.0 MT; webcrawler@NOSPAMexperimental.net; http://aanmelden.ilse.nl/?aanmeld_mode=webhints)", + "Mozilla/3.0 (Slurp.so/Goo; slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Mozilla/3.0 (Slurp/cat; slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Mozilla/3.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Mozilla/3.0 (Vagabondo/1.1 MT; webcrawler@NOSPAMwise-guys.nl; http://webagent.wise-guys.nl/)", + "Mozilla/3.0 (Vagabondo/1.x MT; webagent@wise-guys.nl; http://webagent.wise-guys.nl/)", + "Mozilla/3.0 (Vagabondo/2.0 MT; webcrawler@NOSPAMexperimental.net; http://aanmelden.ilse.nl/?aanmeld_mode=webhints)", + "Mozilla/3.0 (Vagabondo/2.0 MT; webcrawler@NOSPAMwise-guys.nl; http://webagent.wise-guys.nl/)", + "Mozilla/3.01 (Compatible; Links2Go Similarity Engine)", + "Mozilla/4.0", + "Mozilla/4.0 (agadine3.0) www.agada.de", + "Mozilla/4.0 (compatible: AstraSpider V.2.1 : astrafind.com)", + "Mozilla/4.0 (compatible; Vagabondo/2.2; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)", + "Mozilla/4.0 (compatible; Vagabondo/4.0Beta; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)", + "Mozilla/4.0 (compatible; Advanced Email Extractor v2.xx)", + "Mozilla/4.0 (compatible; B_L_I_T_Z_B_O_T)", + "Mozilla/4.0 (compatible; ChristCrawler.com ChristCrawler@ChristCENTRAL.com)", + "Mozilla/4.0 (compatible; crawlx, crawler@trd.overture.com)", + "Mozilla/4.0 (compatible; DAUMOA-video; +http://ws.daum.net/aboutkr.html)", + "Mozilla/4.0 (compatible; FastCrawler3 support-fastcrawler3@fast.no)", + "Mozilla/4.0 (compatible; FDSE robot)", + "Mozilla/4.0 (compatible; GPU p2p crawler http://gpu.sourceforge.net/search_engine.php)", + "Mozilla/4.0 (compatible; grub-client-0.2.x; Crawl your stuff with http://grub.org)", + "Mozilla/4.0 (compatible; grub-client-0.3.x; Crawl your own stuff with http://grub.org)", + "Mozilla/4.0 (compatible; grub-client-2.x)", + "Mozilla/4.0 (compatible; Iplexx Spider/1.0 http://www.iplexx.at)", + "Mozilla/4.0 (compatible; MSIE 4.01; Vonna.com b o t)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; SPV M700; OpVer 19.123.2.733) OrangeBot-Mobile 2008.0 (mobilesearch.support@orange-ftgroup.com)", + "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT; Site Server 3.0 Robot) Indonesia Interactive", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) (samualt9@bigfoot.com)", + "Mozilla/4.0 (compatible; MSIE 5.0; NetNose-Crawler 2.0; A New Search Experience: http://www.netnose.com)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) TrueRobot; 1.5", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) VoilaBot BETA 1.2 (http://www.voila.com/)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) VoilaBot; 1.6", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; DTS Agent", + "Mozilla/4.0 (compatible; MSIE 5.0; www.galaxy.com; www.psychedelix.com)", + "Mozilla/4.0 (compatible; MSIE 5.0; www.galaxy.com; www.psychedelix.com/; http://www.galaxy.com/info/crawler.html)", + "Mozilla/4.0 (compatible; MSIE 5.0; YANDEX)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0; obot)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0; QXW03018)", + "Mozilla/4.0 (compatible; MSIE 6.0 compatible; Asterias Crawler v4; +http://www.singingfish.com/help/spider.html; webmaster@singingfish.com); SpiderThread Revision: 3.10", + "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Skampy/0.9.x [en]", + "Mozilla/4.0 (compatible; MSIE 6.0; TargetSeek/1.0; +http://www.targetgroups.net/TargetSeek.html)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ODP entries t_st; http://tuezilla.de/t_st-odp-entries-agent.html)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ODP links test; http://tuezilla.de/test-odp-links-agent.html)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ZoomSpider.net bot; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; heritrix/1.3.0 http://www.cs.washington.edu/research/networking/websys/)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; QihooBot 1.0 qihoobot@qihoo.net)", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT; MS Search 4.0 Robot)", + "Mozilla/4.0 (compatible; MSIE enviable; DAUMOA 2.0; DAUM Web Robot; Daum Communications Corp., Korea; +http://ws.daum.net/aboutkr.html)", + "Mozilla/4.0 (compatible; MSIE is not me; DAUMOA/1.0.1; DAUM Web Robot; Daum Communications Corp., Korea)", + "Mozilla/4.0 (compatible; NaverBot/1.0; http://help.naver.com/delete_main.asp)", + "Mozilla/4.0 (compatible; SpeedySpider; www.entireweb.com)", + "Mozilla/4.0 (compatible; www.galaxy.com)", + "Mozilla/4.0 (compatible; Y!J; for robot study; keyoshid)", + "Mozilla/4.0 (compatible; Yahoo Japan; for robot study; kasugiya)", + "Mozilla/4.0 (JemmaTheTourist;http://www.activtourist.com)", + "Mozilla/4.0 (MobilePhone SCP-5500/US/1.0) NetFront/3.0 MMP/2.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)", + "Mozilla/4.0 (MobilePhone SCP-5500/US/1.0) NetFront/3.0 MMP/2.0 FAKE (compatible; Googlebot/2.1; http://www.google.com/bot.html)", + "Mozilla/4.0 (Mozilla; http://www.mozilla.org/docs/en/bot.html; master@mozilla.com)", + "Mozilla/4.0 (Sleek Spider/1.2)", + "Mozilla/4.0 compatible FurlBot/Furl Search 2.0 (FurlBot; http://www.furl.net; wn.furlbot@looksmart.net)", + "Mozilla/4.0 compatible ZyBorg/1.0 (wn.zyborg@looksmart.net; http://www.WISEnutbot.com)", + "Mozilla/4.0 compatible ZyBorg/1.0 (ZyBorg@WISEnutbot.com; http://www.WISEnutbot.com)", + "Mozilla/4.0 compatible ZyBorg/1.0 Dead Link Checker (wn.zyborg@looksmart.net; http://www.WISEnutbot.com)", + "Mozilla/4.0 compatible ZyBorg/1.0 for Homepage (ZyBorg@WISEnutbot.com; http://www.WISEnutbot.com)", + "Mozilla/4.0 efp@gmx.net", + "Mozilla/4.0 [en] (Ask Jeeves Corporate Spider)", + "Mozilla/4.0(compatible; Zealbot 1.0)", + "Mozilla/4.04 (compatible; Dulance bot; +http://www.dulance.com/bot.jsp)", + "Mozilla/4.0_(compatible;_MSIE_5.0;_Windows_95)_TrueRobot/1.4 libwww/5.2.8", + "Mozilla/4.0_(compatible;_MSIE_5.0;_Windows_95)_VoilaBot/1.6 libwww/5.3.2", + "Mozilla/4.6 [en] (http://www.cnet.com/)", + "Mozilla/4.7", + "Mozilla/4.7 (compatible; http://eidetica.com/spider)", + "Mozilla/4.7 (compatible; Intelliseek; http://www.intelliseek.com)", + "Mozilla/4.7 (compatible; Whizbang)", + "Mozilla/4.7 (compatible; WhizBang; http://www.whizbang.com/crawler)", + "Mozilla/4.7 [en](BecomeBot@exava.com)", + "Mozilla/4.7 [en](Exabot@exava.com)", + "Mozilla/4.72 [en] (BACS http://www.ba.be)", + "Mozilla/5.0", + "Mozilla/5.0 (+http://www.eurekster.com/mammoth) Mammoth/0.1", + "Mozilla/5.0 (+http://www.sli-systems.com/) Mammoth/0.1", + "Mozilla/5.0 (Clustered-Search-Bot/1.0; support@clush.com; http://www.clush.com/)", + "Mozilla/5.0 (compatible; +http://www.evri.com/evrinid)", + "Mozilla/5.0 (compatible; 008/0.83; http://www.80legs.com/spider.html;) Gecko/2008032620", + "Mozilla/5.0 (compatible; Abonti/0.8 - http://www.abonti.com)", + "Mozilla/5.0 (compatible; aiHitBot/1.0; +http://www.aihit.com/)", + "Mozilla/5.0 (compatible; AnsearchBot/1.x; +http://www.ansearch.com.au/)", + "Mozilla/5.0 (compatible; archive.org_bot/1.10.0 +http://www.loc.gov/minerva/crawl.html)", + "Mozilla/5.0 (compatible; archive.org_bot/1.13.1x http://crawler.archive.org)", + "Mozilla/5.0 (compatible; archive.org_bot/1.5.0-200506132127 http://crawler.archive.org) Hurricane Katrina", + "Mozilla/5.0 (compatible; Ask Jeeves/Teoma; http://about.ask.com/en/docs/about/webmasters.shtml)", + "Mozilla/5.0 (compatible; BecomeBot/1.23; http://www.become.com/webmasters.html)", + "Mozilla/5.0 (compatible; BecomeBot/1.xx; MSIE 6.0 compatible; http://www.become.com/webmasters.html)", + "Mozilla/5.0 (compatible; BecomeBot/2.0beta; http://www.become.com/webmasters.html)", + "Mozilla/5.0 (compatible; BecomeBot/2.x; MSIE 6.0 compatible; http://www.become.com/site_owners.html)", + "Mozilla/5.0 (compatible; BecomeJPBot/2.3; MSIE 6.0 compatible; +http://www.become.co.jp/site_owners.html)", + "Mozilla/5.0 (compatible; BlogRefsBot/0.1; http://www.blogrefs.com/about/bloggers)", + "Mozilla/5.0 (compatible; Bot; +http://pressemitteilung.ws/spamfilter", + "Mozilla/5.0 (compatible; BuzzRankingBot/1.0; +http://www.buzzrankingbot.com/)", + "Mozilla/5.0 (compatible; Charlotte/1.0b; charlotte@betaspider.com)", + "Mozilla/5.0 (compatible; Charlotte/1.0b; http://www.searchme.com/support/)", + "Mozilla/5.0 (compatible; Crawling jpeg; http://www.yama.info.waseda.ac.jp)", + "Mozilla/5.0 (compatible; de/1.13.2 +http://www.de.com)", + "Mozilla/5.0 (compatible; Diffbot/0.1; +http://www.diffbot.com)", + "Mozilla/5.0 (compatible; DNS-Digger-Explorer/1.0; +http://www.dnsdigger.com)", + "Mozilla/5.0 (compatible; DNS-Digger/1.0; +http://www.dnsdigger.com)", + "Mozilla/5.0 (compatible; EARTHCOM.info/2.01; http://www.earthcom.info)", + "Mozilla/5.0 (compatible; EARTHCOM/2.2; +http://enter4u.eu)", + "Mozilla/5.0 (compatible; Exabot Test/3.0; +http://www.exabot.com/go/robot)", + "Mozilla/5.0 (compatible; FatBot 2.0; http://www.thefind.com/main/CrawlerFAQs.fhtml)", + "Mozilla/5.0 (compatible; Galbot/1.0; +http://www.galbot.com/bot.html)", + "mozilla/5.0 (compatible; genevabot http://www.healthdash.com)", + "Mozilla/5.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)", + "mozilla/5.0 (compatible; heritrix/1.0.4 http://innovationblog.com)", + "Mozilla/5.0 (compatible; heritrix/1.10.2 +http://i.stanford.edu/)", + "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://newstin.com/)", + "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://www.page-store.com)", + "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://www.page-store.com) [email:paul@page-store.com]", + "mozilla/5.0 (compatible; heritrix/1.3.0 http://archive.crawler.org)", + "Mozilla/5.0 (compatible; heritrix/1.4.0 +http://www.chepi.net)", + "Mozilla/5.0 (compatible; heritrix/1.4t http://www.truveo.com/)", + "Mozilla/5.0 (compatible; heritrix/1.5.0 http://www.l3s.de/~kohlschuetter/projects/crawling/)", + "Mozilla/5.0 (compatible; heritrix/1.5.0-200506231921 http://pandora.nla.gov.au/crawl.html)", + "Mozilla/5.0 (compatible; heritrix/1.6.0 http://www.worio.com/)", + "Mozilla/5.0 (compatible; heritrix/1.7.0 +http://www.greaterera.com/)", + "Mozilla/5.0 (compatible; heritrix/1.x.x +http://www.accelobot.com)", + "Mozilla/5.0 (compatible; heritrix/2.0.0-RC1 +http://www.aol.com)", + "Mozilla/5.0 (compatible; Hermit Search. Com; +http://www.hermitsearch.com)", + "Mozilla/5.0 (compatible; HyperixScoop/1.3; +http://www.hyperix.com)", + "Mozilla/5.0 (compatible; IDBot/1.0; +http://www.id-search.org/bot.html)", + "Mozilla/5.0 (compatible; InterseekWeb/3.x)", + "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Exabot-Thumbnails)", + "Mozilla/5.0 (compatible; LemSpider 0.1)", + "Mozilla/5.0 (compatible; MojeekBot/2.0; http://www.mojeek.com/bot.html)", + "Mozilla/5.0 (compatible; MSIE 6.0; Podtech Network; crawler_admin@podtech.net)", + "Mozilla/5.0 (compatible; OnetSzukaj/5.0; http://szukaj.onet.pl)", + "Mozilla/5.0 (compatible; PalmeraBot; http://www.links24h.com/help/palmera) Version 0.001", + "Mozilla/5.0 (compatible; pogodak.ba/3.x)", + "Mozilla/5.0 (compatible; Pogodak.hr/3.1)", + "Mozilla/5.0 (compatible; PWeBot/3.1; http://www.programacionweb.net/robot.php)", + "Mozilla/5.0 (compatible; Quantcastbot/1.0; www.quantcast.com)", + "Mozilla/5.0 (compatible; ScoutJet; +http://www.scoutjet.com/)", + "Mozilla/5.0 (compatible; Scrubby/2.2; http://www.scrubtheweb.com/)", + "Mozilla/5.0 (compatible; ShunixBot/1.x.x +http://www.shunix.com/robot.htm)", + "Mozilla/5.0 (compatible; ShunixBot/1.x; http://www.shunix.com/bot.htm)", + "Mozilla/5.0 (compatible; SkreemRBot +http://skreemr.com)", + "Mozilla/5.0 (compatible; SummizeBot +http://www.summize.com)", + "Mozilla/5.0 (compatible; Synoobot/0.9; http://www.synoo.com/search/bot.html)", + "Mozilla/5.0 (compatible; Theophrastus/x.x; http://users.cs.cf.ac.uk/N.A.Smith/theophrastus.php)", + "Mozilla/5.0 (compatible; TridentSpider/3.1)", + "Mozilla/5.0 (compatible; Vagabondo/2.1; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)", + "Mozilla/5.0 (compatible; Webduniabot/1.0; +http://search.webdunia.com/bot.aspx)", + "Mozilla/5.0 (compatible; worio bot heritrix/1.10.0 +http://worio.com)", + "Mozilla/5.0 (compatible; WoW Lemmings Kathune/2.0;http://www.wowlemmings.com/kathune.html)", + "Mozilla/5.0 (compatible; Yahoo! DE Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", + "Mozilla/5.0 (compatible; Yahoo! Slurp China; http://misc.yahoo.com.cn/help.html)", + "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", + "Mozilla/5.0 (compatible; Yoono; http://www.yoono.com/)", + "Mozilla/5.0 (compatible; YoudaoBot/1.0; http://www.youdao.com/help/webmaster/spider/; )", + "Mozilla/5.0 (compatible; Zenbot/1.3; +http://zen.co.za/webmasters/)", + "Mozilla/5.0 (compatible; zermelo +http://www.powerset.com) [email:paul@page-store.com,crawl@powerset.com]", + "Mozilla/5.0 (compatible;archive.org_bot/1.7.1; collectionId=316; Archive-It; +http://www.archive-it.org)", + "Mozilla/5.0 (compatible;archive.org_bot/heritrix-1.9.0-200608171144 +http://pandora.nla.gov.au/crawl.html)", + "Mozilla/5.0 (compatible;MAINSEEK_BOT)", + "Mozilla/5.0 (Slurp/cat; slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Mozilla/5.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Mozilla/5.0 (Twiceler-0.9 http://www.cuill.com/twiceler/robot.html)", + "Mozilla/5.0 (Version: xxxx Type:xx)", + "Mozilla/5.0 (wgao@genieknows.com)", + "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.7) NimbleCrawler 1.11 obeys UserAgent NimbleCrawler For problems contact: crawler_at_dataalchemy.com", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (support.voilabot@orange-ftgroup.com)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (support.voilabot@orange-ftgroup.com)", + "Mozilla/5.0 (Windows;) NimbleCrawler 1.12 obeys UserAgent NimbleCrawler For problems contact: crawler@health", + "Mozilla/5.0 (Windows;) NimbleCrawler 1.12 obeys UserAgent NimbleCrawler For problems contact: crawler@healthline.com", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2.1; aggregator:Spinn3r (Spinn3r 3.1); http://spinn3r.com/robot) Gecko/20021130", + "Mozilla/5.0 URL-Spider", + "Mozilla/5.0 usww.com-Spider-for-w8.net", + "Mozilla/5.0 wgao@genieknows.com", + "Mozilla/5.0 [en] (compatible; Gulper Web Bot 0.2.4 www.ecsl.cs.sunysb.edu/~maxim/cgi-bin/Link/GulperBot)", + "MQbot metaquerier.cs.uiuc.edu/crawler", + "MQBOT/Nutch-0.9-dev (MQBOT Nutch Crawler; http://falcon.cs.uiuc.edu; mqbot@cs.uiuc.edu)", + "msnbot-media/1.0 (+http://search.msn.com/msnbot.htm)", + "msnbot-Products/1.0 (+http://search.msn.com/msnbot.htm)", + "MSNBOT/0.xx (http://search.msn.com/msnbot.htm)", + "msnbot/x.xx ( http://search.msn.com/msnbot.htm)", + "MSNBOT_Mobile MSMOBOT Mozilla/2.0 (compatible; MSIE 4.02; Windows CE; Default)", + "MSNPTC/1.0", + "MSRBOT (http://research.microsoft.com/research/sv/msrbot)", + "multicrawler ( http://sw.deri.org/2006/04/multicrawler/robots.html)", + "MultiText/0.1", + "MusicWalker2.0 ( http://www.somusical.com)", + "MVAClient", + "Mylinea.com Crawler 2.0", + "Naamah 1.0.1/Blogbot (http://blogbot.de/)", + "Naamah 1.0a/Blogbot (http://blogbot.de/)", + "NABOT/5.0", + "nabot_1.0", + "NameOfAgent (CMS Spider)", + "NASA Search 1.0", + "NationalDirectory-WebSpider/1.3", + "NationalDirectoryAddURL/1.0", + "NaverBot-1.0 (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)", + "NaverBot_dloader/1.5", + "NavissoBot", + "NavissoBot/1.7 (+http://navisso.com/)", + "NCSA Beta 1 (http://vias.ncsa.uiuc.edu/viasarchivinginformation.html)", + "Nebullabot/2.2 (http://bot.nebulla.info)", + "NEC Research Agent -- compuman at research.nj.nec.com", + "Net-Seekr Bot/Net-Seekr Bot V1 (http://www.net-seekr.com)", + "NetinfoBot/1.0 (http://netinfo.bg/netinfobot.html)", + "NetLookout/2.24", + "Netluchs/0.8-dev ( ; http://www.netluchs.de/; ___don't___spam_me_@netluchs.de)", + "NetNoseCrawler/v1.0", + "Netprospector JavaCrawler", + "NetResearchServer(http://www.look.com)", + "NetResearchServer/x.x(loopimprovements.com/robot.html)", + "NetSeer/Nutch-0.9 (NetSeer Crawler; http://www.netseer.com; crawler@netseer.com)", + "NetSprint -- 2.0", + "NetWhatCrawler/0.06-dev (NetWhatCrawler from NetWhat.com; http://www.netwhat.com; support@netwhat.com)", + "NetZippy", + "NextGenSearchBot 1 (for information visit http://www.eliyon.com/NextGenSearchBot)", + "NextopiaBOT (+http://www.nextopia.com) distributed crawler client beta v0.x", + "NG-Search/0.90 (NG-SearchBot; http://www.ng-search.com; )", + "NG/1.0", + "NG/4.0.1229", + "NITLE Blog Spider/0.01", + "Noago Spider", + "Nokia-WAPToolkit/1.2 googlebot(at)googlebot.com", + "Nokia6610/1.0 (3.09) Profile/MIDP-1.0 Configuration/CLDC-1.0 (compatible;YahooSeeker/M1A1-R2D2; http://help.yahoo.com/help/us/ysearch/crawling/crawling-01.html)", + "NokodoBot/1.x (+http://nokodo.com/bot.htm)", + "Norbert the Spider(Burf.com)", + "noxtrumbot/1.0 (crawler@noxtrum.com)", + "noyona_0_1", + "NP/0.1 (NP; http://www.nameprotect.com; npbot@nameprotect.com)", + "NPBot (http://www.nameprotect.com/botinfo.html)", + "NPBot-1/2.0", + "Nsauditor/1.x", + "nsyght.com/Nutch-1.0-dev (nsyght.com; Nsyght.com)", + "nsyght.com/Nutch-x.x (nsyght.com; search.nsyght.com)", + "nttdirectory_robot/0.9 (super-robot@super.navi.ocn.ne.jp)", + "nuSearch Spider www.nusearch.com (compatible; MSIE 4.01)", + "NuSearch Spider (compatible; MSIE 6.0)", + "NuSearch Spider www.nusearch.com", + "Nutch", + "Nutch crawler/Nutch-0.9 (picapage.com; admin@picapage.com)", + "Nutch/Nutch-0.9 (Eurobot; http://www.ayell.eu )", + "NutchCVS/0.0x-dev (Nutch; http://www.nutch.org/docs/bot.html; nutch-agent@lists.sourceforge.net)", + "NutchCVS/0.7.1 (Nutch running at UW; http://www.nutch.org/docs/en/bot.html; sycrawl@cs.washington.edu)", + "NutchEC2Test/Nutch-0.9-dev (Testing Nutch on Amazon EC2.; http://lucene.apache.org/nutch/bot.html; ec2test at lucene.com)", + "NutchOrg/0.0x-dev (Nutch; http://www.nutch.org/docs/bot.html; nutch-agent@lists.sourceforge.net)", + "nutchsearch/Nutch-0.9 (Nutch Search 1.0; herceg_novi at yahoo dot com)", + "NutchVinegarCrawl/Nutch-0.8.1 (Vinegar; http://www.cs.washington.edu; eytanadar at gmail dot com)", + "obidos-bot (just looking for books.)", + "ObjectsSearch/0.01-dev (ObjectsSearch;http://www.ObjectsSearch.com/bot.html; support@thesoftwareobjects.com)", + "ObjectsSearch/0.0x (ObjectsSearch; http://www.ObjectsSearch.com/bot.html; support@thesoftwareobjects.com)", + "oBot ((compatible;Win32))", + "Ocelli/1.x (http://www.globalspec.com/Ocelli)", + "Octora Beta - www.octora.com", + "Octora Beta Bot - www.octora.com", + "OmniExplorer_Bot/1.0x (+http://www.omni-explorer.com) Internet CategorizerOmniExplorer http://www.omni-explorer.com/ car & shopping search (64.62.175.xxx)", + "OmniExplorer_Bot/1.0x (+http://www.omni-explorer.com) Job Crawler", + "OmniExplorer_Bot/1.1x (+http://www.omni-explorer.com) Torrent Crawler", + "OmniExplorer_Bot/x.xx (+http://www.omni-explorer.com) WorldIndexer", + "Onet.pl SA- http://szukaj.onet.pl", + "OntoSpider/1.0 libwww-perl/5.65", + "OOZBOT/0.20 ( http://www.setooz.com/oozbot.html ; agentname at setooz dot_com )", + "OpenAcoon v4.0.x (www.openacoon.de)", + "Openbot/3.0+(robot-response@openfind.com.tw;+http://www.openfind.com.tw/robot.html)", + "Openfind data gatherer- Openbot/3.0+(robot-response@openfind.com.tw;+http://www.openfind.com.tw/robot.html)", + "Openfind Robot/1.1A2", + "OpenISearch/1.x (www.openisearch.com)", + "OpenTaggerBot (http://www.opentagger.com/opentaggerbot.htm)", + "OpenTextSiteCrawler/2.9.2", + "OpenWebSpider/0.x.x (http://www.openwebspider.org)", + "OpenWebSpider/x", + "OpidooBOT (larbin2.6.3@unspecified.mail)", + "Oracle Ultra Search", + "OrangeSpider", + "Orbiter/T-2.0 (+http://www.dailyorbit.com/bot.htm)", + "Overture-WebCrawler/3.8/Fresh (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)", + "ozelot/2.7.3 (Search engine indexer; www.flying-cat.de/ozelot; ozelot@flying-cat.de)", + "PADLibrary Spider", + "PageBitesHyperBot/600 (http://www.pagebites.com/)", + "Pagebull http://www.pagebull.com/", + "page_verifier (http://www.securecomputing.com/goto/pv)", + "parallelContextFocusCrawler1.1parallelContextFocusCrawler1.1", + "ParaSite/1.0b (http://www.ianett.com/parasite/)", + "Patwebbot (http://www.herz-power.de/technik.html)", + "PBrowse 1.4b", + "pd02_1.0.0 pd02_1.0.0@dzimi@post.sk", + "PEERbot www.peerbot.com", + "PEval 1.4b", + "PicoSearch/1.0", + "Piffany_Web_Scraper_v0.x", + "Piffany_Web_Spider_v0.x", + "pipeLiner/0.3a (PipeLine Spider;http://www.pipeline-search.com/webmaster.html; webmaster'at'pipeline-search.com)", + "pipeLiner/0.xx (PipeLine Spider; http://www.pipeline-search.com/webmaster.html)", + "Pita", + "PJspider/3.0 (pjspider@portaljuice.com; http://www.portaljuice.com)", + "PlagiarBot/1.0", + "PluckFeedCrawler/2.0 (compatible; Mozilla 4.0; MSIE 5.5; http://www.pluck.com; 1 subscribers)", + "Pluggd/Nutch-0.9 (automated crawler http://www.pluggd.com;support at pluggd dot com)", + "Poirot", + "polybot 1.0 (http://cis.poly.edu/polybot/)", + "Pompos/1.x http://dir.com/pompos.html", + "Pompos/1.x pompos@iliad.fr", + "Popdexter/1.0", + "Port Huron Labs", + "PortalBSpider/2.0 (spider@portalb.com)", + "potbot 1.0", + "PRCrawler/Nutch-0.9 (data mining development project; crawler@projectrialto.com)", + "PrivacyFinder Cache Bot v1.0", + "PrivacyFinder/1.1", + "Production Bot 0116B", + "Production Bot 2016B", + "Production Bot DOT 3016B", + "Program Shareware 1.0.2", + "Project XP5 [2.03.07-111203]", + "PROve AnswerBot 4.0", + "ProWebGuide Link Checker (http://www.prowebguide.com)", + "psbot/0.1 (+http://www.picsearch.com/bot.html)", + "PSurf15a 11", + "PSurf15a 51", + "PSurf15a VA", + "psycheclone", + "PubCrawl (pubcrawl.stanford.edu)", + "pulseBot (pulse Web Miner)", + "PWeBot/1.2 Inspector (http://www.programacionweb.net/robot.php)", + "PycURL", + "Python-urllib/1.1x", + "Python-urllib/2.0a1", + "Qango.com Web Directory (http://www.qango.com/)", + "QEAVis Agent/Nutch-0.9 (Quantitative Evaluation of Academic Websites Visibility; http://nlp.uned.es/qeavis", + "QPCreep Test Rig ( We are not indexing- just testing )", + "QuepasaCreep ( crawler@quepasacorp.com )", + "QuepasaCreep v0.9.1x", + "QueryN Metasearch", + "QweeryBot/3.01 ( http://qweerybot.qweery.nl)", + "Qweery_robot.txt_CheckBot/3.01 (http://qweerybot.qweery.com)", + "R6_CommentReader_(www.radian6.com/crawler)", + "R6_FeedFetcher_(www.radian6.com/crawler)", + "rabaz (rabaz at gigabaz dot com)", + "RaBot/1.0 Agent-admin/phortse@hanmail.net", + "ramBot xtreme x.x", + "RAMPyBot - www.giveRAMP.com/0.1 (RAMPyBot - www.giveRAMP.com; http://www.giveramp.com/bot.html; support@giveRAMP.com)", + "RAMPyBot/0.8-dev (Nutch; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)", + "Rankivabot/3.2 (www.rankiva.com; 3.2; vzmxikn)", + "Rational SiteCheck (Windows NT)", + "Reaper [2.03.10-031204] (http://www.sitesearch.ca/reaper/)", + "Reaper/2.0x (+http://www.sitesearch.ca/reaper)", + "RedCarpet/1.2 (http://www.redcarpet-inc.com/robots.html)", + "RedCell/0.1 (InfoSec Search Bot (Coming Soon); http://www.telegenetic.net/bot.html; lhall@telegenetic.net)", + "RedCell/0.1 (RedCell; telegenetic.net/bot.html; lhall_at_telegenetic.net)", + "RedKernel WWW-Spider 2/0 (+http://www-spider.redkernel-softwares.com/)", + "rico/0.1", + "RixBot (http://babelserver.org/rix)", + "RoboCrawl (http://www.canadiancontent.net)", + "RoboCrawl (www.canadiancontent.net)", + "RoboPal (http://www.findpal.com/)", + "Robot/www.pj-search.com", + "Robot: NutchCrawler- Owner: wdavies@acm.org", + "Robot@SuperSnooper.Com", + "Robozilla/1.0", + "Rotondo/3.1 libwww/5.3.1", + "RRC (crawler_admin@bigfoot.com)", + "RSSMicro.com RSS/Atom Feed Robot", + "RSurf15a 41", + "RSurf15a 51", + "RSurf15a 81", + "RufusBot (Rufus Web Miner; http://64.124.122.252/feedback.html)", + "RufusBot (Rufus Web Miner; http://www.webaroo.com/rooSiteOwners.html)", + "sait/Nutch-0.9 (SAIT Research; http://www.samsung.com)", + "SandCrawler - Compatibility Testing", + "SapphireWebCrawler/1.0 (Sapphire Web Crawler using Nutch; http://boston.lti.cs.cmu.edu/crawler/; mhoy@cs.cmu.edu)", + "SapphireWebCrawler/Nutch-1.0-dev (Sapphire Web Crawler using Nutch; http://boston.lti.cs.cmu.edu/crawler/; mhoy@cs.cmu.edu)", + "savvybot/0.2", + "SBIder/0.7 (SBIder; http://www.sitesell.com/sbider.html; http://support.sitesell.com/contact-support.html)", + "SBIder/0.8-dev (SBIder; http://www.sitesell.com/sbider.html; http://support.sitesell.com/contact-support.html)", + "ScanWeb", + "ScholarUniverse/0.8 (Nutch;+http://scholaruniverse.com/bot.jsp; fetch-agent@scholaruniverse.com)", + "schwarzmann.biz-Spider_for_paddel.org+(http://www.innerprise.net/usp-spider.asp)", + "ScollSpider/2.0 (+http://www.webwobot.com/ScollSpider.php)", + "Scooter-3.0.EU", + "Scooter-3.0.FS", + "Scooter-3.0.HD", + "Scooter-3.0.VNS", + "Scooter-3.0QI", + "Scooter-3.2", + "Scooter-3.2.BT", + "Scooter-3.2.DIL", + "Scooter-3.2.EX", + "Scooter-3.2.JT", + "Scooter-3.2.NIV", + "Scooter-3.2.SF0", + "Scooter-3.2.snippet", + "Scooter-3.3dev", + "Scooter-ARS-1.1", + "Scooter-ARS-1.1-ih", + "scooter-venus-3.0.vns", + "Scooter-W3-1.0", + "Scooter-W3.1.2", + "Scooter/1.0", + "Scooter/1.0 scooter@pa.dec.com", + "Scooter/1.1 (custom)", + "Scooter/2.0 G.R.A.B. V1.1.0", + "Scooter/2.0 G.R.A.B. X2.0", + "Scooter/3.3", + "Scooter/3.3.QA.pczukor", + "Scooter/3.3.vscooter", + "Scooter/3.3_SF", + "Scooter2_Mercator_x-x.0", + "Scooter_bh0-3.0.3", + "Scooter_trk3-3.0.3", + "ScoutAbout", + "ScoutAnt/0.1; +http://www.ant.com/what_is_ant.com/", + "scoutmaster", + "Scrubby/2.x (http://www.scrubtheweb.com/)", + "Scrubby/3.0 (+http://www.scrubtheweb.com/help/technology.html)", + "Search+", + "Search-Engine-Studio", + "search.ch V1.4", + "search.ch V1.4.2 (spiderman@search.ch; http://www.search.ch)", + "Search/1.0 (http://www.innerprise.net/es-spider.asp)", + "searchbot admin@google.com", + "SearchByUsa/2 (SearchByUsa; http://www.SearchByUsa.com/bot.html; info@SearchByUsa.com)", + "SearchdayBot", + "SearchExpress Spider0.99", + "SearchGuild/DMOZ/Experiment (searchguild@gmail.com)", + "SearchGuild_DMOZ_Experiment (chris@searchguild.com)", + "Searchit-Now Robot/2.2 (+http://www.searchit-now.co.uk)", + "Searchmee! Spider v0.98a", + "SearchSight/2.0 (http://SearchSight.com/)", + "SearchSpider.com/1.1", + "Searchspider/1.2 (SearchSpider; http://www.searchspider.com; webmaster@searchspider.com)", + "SearchTone2.0 - IDEARE", + "Seekbot/1.0 (http://www.seekbot.net/bot.html) HTTPFetcher/0.3", + "Seekbot/1.0 (http://www.seekbot.net/bot.html) RobotsTxtFetcher/1.0 (XDF)", + "Seekbot/1.0 (http://www.seekbot.net/bot.html) RobotsTxtFetcher/1.2", + "Seeker.lookseek.com", + "Semager/1.1 (http://www.semager.de/blog/semager-bots/)", + "Semager/1.x (http://www.semager.de)", + "Sensis Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)", + "Sensis.com.au Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)", + "SeznamBot/1.0", + "SeznamBot/1.0 (+http://fulltext.seznam.cz/)", + "SeznamBot/2.0-test (+http://fulltext.sblog.cz/)", + "ShablastBot 1.0", + "Shim Crawler", + "Shim-Crawler(Mozilla-compatible; http://www.logos.ic.i.u-tokyo.ac.jp/crawler/; crawl@logos.ic.i.u-tokyo.ac.jp)", + "ShopWiki/1.0 ( +http://www.shopwiki.com/)", + "ShopWiki/1.0 ( +http://www.shopwiki.com/wiki/Help:Bot)", + "Shoula.com Crawler 2.0", + "SietsCrawler/1.1 (+http://www.siets.biz)", + "Sigram/Nutch-1.0-dev (Test agent for Nutch development; http://www.sigram.com/bot.html; bot at sigram dot com)", + "Siigle Orumcex v.001 Turkey (http://www.siigle.com)", + "silk/1.0", + "silk/1.0 (+http://www.slider.com/silk.htm)/3.7", + "Sirketcebot/v.01 (http://www.sirketce.com/bot.html)", + "SiteSpider +(http://www.SiteSpider.com/)", + "SiteTruth.com site rating system", + "SiteXpert", + "Skampy/0.9.x (http://www.skaffe.com/skampy-info.html)", + "Skimpy/0.x (http://www.skaffe.com/skampy-info.html)", + "Skywalker/0.1 (Skywalker; anonymous; anonymous)", + "Slarp/0.1", + "Slider_Search_v1-de", + "Slurp/2.0 (slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Slurp/2.0-KiteWeekly (slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Slurp/si (slurp@inktomi.com; http://www.inktomi.com/slurp.html)", + "Slurpy Verifier/1.0", + "SlySearch (slysearch@slysearch.com)", + "SlySearch/1.0 http://www.plagiarism.org/crawler/robotinfo.html", + "SlySearch/1.x http://www.slysearch.com", + "smartwit.com", + "SmiffyDCMetaSpider/1.0", + "snap.com beta crawler v0", + "Snapbot/1.0", + "Snapbot/1.0 (Snap Shots, +http://www.snap.com)", + "SnykeBot/0.6 (http://www.snyke.com)", + "SocSciBot ()", + "SoftHypermarketFileCheckBot/1.0+(+http://www.softhypermaket.com)", + "sogou develop spider", + "Sogou Orion spider/3.0(+http://www.sogou.com/docs/help/webmasters.htm#07)", + "sogou spider", + "Sogou web spider/3.0(+http://www.sogou.com/docs/help/webmasters.htm#07)", + "sohu agent", + "sohu-search", + "Sosospider+(+http://help.soso.com/webspider.htm)", + "speedfind ramBot xtreme 8.1", + "Speedy Spider (Beta/x.x; speedy@entireweb.com)", + "Speedy Spider (Entireweb; Beta/1.0; http://www.entireweb.com/about/search_tech/speedyspider/)", + "Speedy_Spider (http://www.entireweb.com)", + "Sphere Scout&v4.0 - scout at sphere dot com", + "Sphider", + "Spida/0.1", + "Spider-Sleek/2.0 (+http://search-info.com/linktous.html)", + "spider.batsch.com", + "spider.yellopet.com - www.yellopet.com", + "Spider/maxbot.com admin@maxbot.com", + "SpiderKU/0.x", + "SpiderMan", + "SpiderMonkey/7.0x (SpiderMonkey.ca info at http://spidermonkey.ca/sm.shtml)", + "Spinne/2.0", + "Spinne/2.0 med", + "Spinne/2.0 med_AH", + "Spock Crawler (http://www.spock.com/crawler)", + "sportsuchmaschine.de-Robot (Version: 1.02- powered by www.sportsuchmaschine.de)", + "sproose/0.1-alpha (sproose crawler; http://www.sproose.com/bot.html; crawler@sproose.com)", + "Sqworm/2.9.81-BETA (beta_release; 20011102-760; i686-pc-linux-gnu)", + "Sqworm/2.9.85-BETA (beta_release; 20011115-775; i686-pc-linux-gnu)", + "SSurf15a 11 ", + "StackRambler/x.x ", + "stat statcrawler@gmail.com", + "Steeler/1.x (http://www.tkl.iis.u-tokyo.ac.jp/~crawler/)", + "Steeler/3.3 (http://www.tkl.iis.u-tokyo.ac.jp/~crawler/)", + "Strategic Board Bot (+http://www.strategicboard.com)", + "Strategic Board Bot (+http://www.strategicboard.com)", + "Submission Spider at surfsafely.com", + "suchbaer.de", + "suchbaer.de (CrawlerAgent v0.103)", + "suchbot", + "Suchknecht.at-Robot", + "suchpadbot/1.0 (+http://www.suchpad.de)", + "SurferF3 1/0", + "suzuran", + "Swooglebot/2.0. (+http://swoogle.umbc.edu/swooglebot.htm)", + "SWSBot-Images/1.2 http://www.smartwaresoft.com/swsbot12.html", + "SygolBot http://www.sygol.net", + "SynoBot", + "Syntryx ANT Scout Chassis Pheromone; Mozilla/4.0 compatible crawler", + "Szukacz/1.x", + "Szukacz/1.x (robot; www.szukacz.pl/jakdzialarobot.html; szukacz@proszynski.pl)", + "tags2dir.com/0.8 (+http://tags2dir.com/directory/)", + "Tagword (http://tagword.com/dmoz_survey.php)", + "Talkro Web-Shot/1.0 (E-mail: webshot@daumsoft.com- Home: http://222.122.15.190/webshot)", + "TCDBOT/Nutch-0.8 (PhD student research;http://www.tcd.ie; mcgettrs at t c d dot IE)", + "TECOMAC-Crawler/0.x", + "Tecomi Bot (http://www.tecomi.com/bot.htm)", + "Teemer (NetSeer, Inc. is a Los Angeles based Internet startup company.; http://www.netseer.com/crawler.html; crawler@netseer.com)", + "Teoma MP", + "teomaagent crawler-admin@teoma.com", + "teomaagent1 [crawler-admin@teoma.com]", + "teoma_agent1", + "Teradex Mapper; mapper@teradex.com; http://www.teradex.com", + "terraminds-bot/1.0 (support@terraminds.de)", + "TerrawizBot/1.0 (+http://www.terrawiz.com/bot.html)", + "Test spider", + "TestCrawler/Nutch-0.9 (Testing Crawler for Research ; http://balihoo.com/index.aspx; tgautier at balihoo dot com)", + "TheRarestParser/0.2a (http://therarestwords.com/)", + "TheSuBot/0.1 (www.thesubot.de)", + "thumbshots-de-Bot (Version: 1.02- powered by www.thumbshots.de)", + "timboBot/0.9 http://www.breakingblogs.com/timbo_bot.html", + "TinEye/1.1 (http://tineye.com/crawler.html)", + "tivraSpider/1.0 (crawler@tivra.com)", + "TJG/Spider", + "Tkensaku/x.x(http://www.tkensaku.com/q.html)", + "Topodia/1.2-dev (Topodia - Crawler for HTTP content indexing; http://www.topodia.com/; support@topodia.com)", + "Toutatis x-xx.x (hoppa.com)", + "Toutatis x.x (hoppa.com)", + "Toutatis x.x-x", + "traazibot/testengine (+http://www.traazi.de)", + "Trampelpfad-Spider", + "Trampelpfad-Spider-v0.1", + "TSurf15a 11", + "Tumblr/1.0 RSS syndication (+http://www.tumblr.com/) (support@tumblr.com)", + "TurnitinBot/x.x (http://www.turnitin.com/robot/crawlerinfo.html)", + "Turnpike Emporium LinkChecker/0.1", + "TutorGig/1.5 (+http://www.tutorgig.com/crawler)", + "Tutorial Crawler 1.4 (http://www.tutorgig.com/crawler)", + "Twiceler www.cuill.com/robots.html", + "Twiceler-0.9 http://www.cuill.com/twiceler/robot.html", + "Tycoon Agent/Nutch-1.0-dev", + "TygoBot", + "TygoProwler", + "UIowaCrawler/1.0", + "UKWizz/Nutch-0.8.1 (UKWizz Nutch crawler; http://www.ukwizz.com/)", + "Ultraseek", + "Under the Rainbow 2.2", + "UofTDB_experiment (leehyun@cs.toronto.edu)", + "updated/0.1-alpha (updated crawler; http://www.updated.com; crawler@updated.com)", + "updated/0.1beta (updated.com; http://www.updated.com; crawler@updated.om)", + "Uptimebot", + "UptimeBot(www.uptimebot.com)", + "URL Spider Pro/x.xx (innerprise.net)", + "urlfan-bot/1.0; +http://www.urlfan.com/site/bot/350.html", + "URL_Spider_Pro/x.x", + "URL_Spider_Pro/x.x+(http://www.innerprise.net/usp-spider.asp)", + "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)", + "User-Agent: Mozilla/4.0 (SKIZZLE! Distributed Internet Spider v1.0 - www.SKIZZLE.com)", + "USyd-NLP-Spider (http://www.it.usyd.edu.au/~vinci/bot.html)", + "VadixBot", + "Vagabondo-WAP/2.0 (webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)/1.0 Profile", + "Vagabondo/1.x MT (webagent@wise-guys.nl)", + "Vagabondo/2.0 MT", + "Vagabondo/2.0 MT (webagent at wise-guys dot nl)", + "Vagabondo/2.0 MT (webagent@NOSPAMwise-guys.nl)", + "Vagabondo/3.0 (webagent at wise-guys dot nl)", + "Vakes/0.01 (Vakes; http://www.vakes.com/; search@vakes.com)", + "versus 0.2 (+http://versus.integis.ch)", + "versus crawler eda.baykan@epfl.ch", + "VeryGoodSearch.com.DaddyLongLegs", + "verzamelgids.nl - Networking4all Bot/x.x", + "Verzamelgids/2.2 (http://www.verzamelgids.nl)", + "Vespa Crawler", + "VisBot/2.0 (Visvo.com Crawler; http://www.visvo.com/bot.html; bot@visvo.com)", + "Vision Research Lab image spider at vision.ece.ucsb.edu", + "VMBot/0.x.x (VMBot; http://www.VerticalMatch.com/; vmbot@tradedot.com)", + "Vortex/2.2 (+http://marty.anstey.ca/robots/vortex/)", + "voyager-hc/1.0", + "voyager/1.0", + "voyager/2.0 (http://www.kosmix.com/html/crawler.html)", + "VSE/1.0 (testcrawler@hotmail.com)", + "VSE/1.0 (testcrawler@vivisimo.com)", + "vspider", + "vspider/3.x", + "VWBOT/Nutch-0.9-dev (VWBOT Nutch Crawler; http://vwbot.cs.uiuc.edu;+vwbot@cs.uiuc.edu", + "W3SiteSearch Crawler_v1.1 http://www.w3sitesearch.de", + "wadaino.jp-crawler 0.2 (http://wadaino.jp/)", + "Wavefire/0.8-dev (Wavefire; http://www.wavefire.com; info@wavefire.com)", + "Waypath development crawler - info at waypath dot com", + "Waypath Scout v2.x - info at waypath dot com", + "Web Snooper", + "web2express.org/Nutch-0.9-dev (leveled playing field; http://web2express.org/; info at web2express.org)", + "WebAlta Crawler/1.2.1 (http://www.webalta.ru/bot.html)", + "WebarooBot (Webaroo Bot; http://64.124.122.252/feedback.html)", + "WebarooBot (Webaroo Bot; http://www.webaroo.com/rooSiteOwners.html)", + "webbandit/4.xx.0", + "Webclipping.com", + "WebCompass 2.0", + "WebCorp/1.0", + "webcrawl.net", + "WebFindBot(http://www.web-find.com)", + "Webglimpse 2.xx.x (http://webglimpse.net)", + "Weblog Attitude Diffusion 1.0", + "webmeasurement-bot, http://rvs.informatik.uni-leipzig.de", + "WebRankSpider/1.37 (+http://ulm191.server4you.de/crawler/)", + "WebSearch.COM.AU/3.0.1 (The Australian Search Engine; http://WebSearch.COM.AU; Search@WebSearch.COM.AU)", + "WebSearchBench WebCrawler v0.1(Experimental)", + "WebsiteWorth v1.0", + "Webspinne/1.0 webmaster@webspinne.de", + "Websquash.com (Add url robot)", + "WebStat/1.0 (Unix; beta; 20040314)", + "Webster v0.3 ( http://webster.healeys.net/ )", + "WebVac (webmaster@pita.stanford.edu)", + "Webverzeichnis.de - Telefon: 01908 / 26005", + "WebVulnCrawl.unknown/1.0 libwww-perl/5.803", + "Wells Search II", + "WEP Search 00", + "WFARC", + "whatUseek_winona/3.0", + "WhizBang! Lab", + "Willow Internet Crawler by Twotrees V2.1", + "WinHTTP Example/1.0", + "WinkBot/0.06 (Wink.com search engine web crawler; http://www.wink.com/Wink:WinkBot; winkbot@wink.com)", + "WIRE/0.11 (Linux; i686; Bot,Robot,Spider,Crawler,aromano@cli.di.unipi.it)", + "WIRE/0.x (Linux; i686; Bot,Robot,Spider,Crawler)", + "WISEbot/1.0 (WISEbot@koreawisenut.com; http://wisebot.koreawisenut.com)", + "worio heritrix bot (+http://worio.com/)", + "woriobot ( http://www.worio.com/)", + "WorldLight", + "Wotbox/alpha0.6 (bot@wotbox.com; http://www.wotbox.com)", + "Wotbox/alpha0.x.x (bot@wotbox.com; http://www.wotbox.com) Java/1.4.1_02", + "WSB WebCrawler V1.0 (Beta)- cl@cs.uni-dortmund.de", + "WSB, http://websearchbench.cs.uni-dortmund.de", + "wume_crawler/1.1 (http://wume.cse.lehigh.edu/~xiq204/crawler/)", + "Wwlib/Linux", + "www.arianna.it", + "WWWeasel Robot v1.00 (http://wwweasel.de)", + "wwwster/1.x (Beta- mailto:gue@cis.uni-muenchen.de)", + "X-Crawler ", + "xirq/0.1-beta (xirq; http://www.xirq.com; xirq@xirq.com)", + "xyro_(xcrawler@cosmos.inria.fr)", + "Y!J-BSC/1.0 (http://help.yahoo.co.jp/help/jp/search/indexing/indexing-15.html)", + "Y!J-SRD/1.0", + "Y!J/1.0 (http://help.yahoo.co.jp/help/jp/search/indexing/indexing-15.html)", + "yacy (www.yacy.net; v20040602; i386 Linux 2.4.26-gentoo-r13; java 1.4.2_06; MET/en)", + "yacybot (x86 Windows XP 5.1; java 1.5.0_06; Europe/de) yacy.net", + "Yahoo Pipes 1.0", + "Yahoo! Mindset", + "Yahoo-Blogs/v3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/ysearch/crawling/crawling-02.html )", + "Yahoo-MMAudVid/1.0 (mms dash mmaudvidcrawler dash support at yahoo dash inc dot com)", + "Yahoo-MMAudVid/2.0(mms dash mm aud vid crawler dash support at yahoo dash inc.com ;Mozilla 4.0 compatible; MSIE 7.0;Windows NT 5.0; .NET CLR 2.0)", + "Yahoo-MMCrawler/3.x (mm dash crawler at trd dot overture dot com)", + "Yahoo-Test/4.0", + "Yahoo-VerticalCrawler-FormerWebCrawler/3.9 crawler at trd dot overture dot com; http://www.alltheweb.com/help/webmaster/crawler", + "YahooFeedSeeker/2.0 (compatible; Mozilla 4.0; MSIE 5.5; http://publisher.yahoo.com/rssguide)", + "YahooSeeker-Testing/v3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://search.yahoo.com/)", + "YahooSeeker/1.0 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/shop/merchant/)", + "YahooSeeker/1.0 (compatible; Mozilla 4.0; MSIE 5.5; http://search.yahoo.com/yahooseeker.html)", + "YahooSeeker/1.1 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/shop/merchant/)", + "YahooSeeker/bsv3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/ysearch/crawling/crawling-02.html )", + "YahooSeeker/CafeKelsa-dev (compatible; Konqueror/3.2; FreeBSD ;cafekelsa-dev-webmaster@yahoo-inc.com )", + "Yandex/1.01.001 (compatible; Win16; I)", + "Yanga WorldSearch Bot v1.1/beta (http://www.yanga.co.uk/)", + "yarienavoir.net/0.2", + "Yeti", + "Yeti/0.01 (nhn/1noon, yetibot@naver.com, check robots.txt daily and follows it)", + "Yeti/1.0 (NHN Corp.; http://help.naver.com/robots/)", + "yggdrasil/Nutch-0.9 (yggdrasil biorelated search engine; www dot biotec dot tu minus dresden do de slash schroeder; heiko dot dietze at biotec dot tu minus dresden dot de)", + "YodaoBot/1.0 (http://www.yodao.com/help/webmaster/spider/; )", + "yoofind/yoofind-0.1-dev (yoono webcrawler; http://www.yoono.com ; MyEmail)", + "yoogliFetchAgent/0.1", + "yoono/1.0 web-crawler/1.0", + "YottaCars_Bot/4.12 (+http://www.yottacars.com) Car Search Engine ", + "YottaShopping_Bot/4.12 (+http://www.yottashopping.com) Shopping Search Engine", + "Zao-Crawler", + "Zao-Crawler 0.2b", + "Zao/0.1 (http://www.kototoi.org/zao/)", + "ZBot/1.00 (icaulfield@zeus.com)", + "Zearchit", + "ZeBot_lseek.net (bot@ze.bz)", + "ZeBot_www.ze.bz (ze.bz@hotmail.com)", + "zedzo.digest/0.1 (http://www.zedzo.com/)", + "zermelo Mozilla/5.0 compatible; heritrix/1.12.1 (+http://www.powerset.com) [email:crawl@powerset.com,email:paul@page-store.com]", + "zerxbot/Version 0.6 libwww-perl/5.79", + "Zeus ThemeSite Viewer Webster Pro V2.9 Win32", + "Zeus xxxxx Webster Pro V2.9 Win32", + "Zeusbot/0.07 (Ulysseek's web-crawling robot; http://www.zeusbot.com; agent@zeusbot.com)", + "ZipppBot/0.xx (ZipppBot; http://www.zippp.net; webmaster@zippp.net)", + "ZIPPPCVS/0.xx (ZipppBot/.xx;http://www.zippp.net; webmaster@zippp.net)", + "Zippy v2.0 - Zippyfinder.com", + "ZoomSpider - wrensoft.com", + "zspider/0.9-dev http://feedback.redkolibri.com/", + "ZyBorg/1.0 (ZyBorg@WISEnut.com; http://www.WISEnut.com)"] + end +end diff --git a/vendor/impressionist/app/models/impressionist/impressionable.rb b/vendor/impressionist/app/models/impressionist/impressionable.rb new file mode 100644 index 00000000..f5cbd3b0 --- /dev/null +++ b/vendor/impressionist/app/models/impressionist/impressionable.rb @@ -0,0 +1,64 @@ +module Impressionist + module Impressionable + extend ActiveSupport::Concern + + module ClassMethods + attr_accessor :impressionist_cache_options + @impressionist_cache_options = nil + + def impressionist_counter_cache_options + if @impressionist_cache_options + options = { :column_name => :impressions_count, :unique => false } + options.merge!(@impressionist_cache_options) if @impressionist_cache_options.is_a?(Hash) + options + end + end + + def impressionist_counter_caching? + impressionist_counter_cache_options.present? + end + + def counter_caching? + ::ActiveSupport::Deprecation.warn("#counter_caching? is deprecated; please use #impressionist_counter_caching? instead") + impressionist_counter_caching? + end + end + + def impressionable? + true + end + + def impressionist_count(options={}) + # options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now) + # imps = options[:start_date].blank? ? impressions : impressions.where("created_at>=? and created_at<=?",options[:start_date],options[:end_date]) + # options[:filter] == :all ? imps.count : imps.count(options[:filter], :distinct => true) + options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now) + imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date]) + options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count + end + + def update_impressionist_counter_cache + cache_options = self.class.impressionist_counter_cache_options + column_name = cache_options[:column_name].to_sym + count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count + update_attribute(column_name, count) + end + + # OLD METHODS - DEPRECATE IN V0.5 + def impression_count(start_date=nil,end_date=Time.now) + impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=>:all}) + end + + def unique_impression_count(start_date=nil,end_date=Time.now) + impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :request_hash}) + end + + def unique_impression_count_ip(start_date=nil,end_date=Time.now) + impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :ip_address}) + end + + def unique_impression_count_session(start_date=nil,end_date=Time.now) + impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :session_hash}) + end + end +end diff --git a/vendor/impressionist/impressionist.gemspec b/vendor/impressionist/impressionist.gemspec new file mode 100644 index 00000000..e462fe73 --- /dev/null +++ b/vendor/impressionist/impressionist.gemspec @@ -0,0 +1,27 @@ +# encoding: utf-8 +require File.expand_path('../lib/impressionist/version', __FILE__) + +Gem::Specification.new do |s| + s.add_dependency 'httpclient', '~> 2.2' + s.add_dependency 'nokogiri', '~> 1.5' + s.add_development_dependency 'capybara' + s.add_development_dependency 'rake', '>= 0.9' + s.add_development_dependency 'rails', '~>3.1' + s.add_development_dependency 'rdoc', '>= 2.4.2' + s.add_development_dependency 'rspec-rails' + s.add_development_dependency 'simplecov' + s.add_development_dependency 'sqlite3' + s.add_development_dependency 'systemu' + s.authors = ["johnmcaliley"] + s.description = "Log impressions from controller actions or from a model" + s.email = "john.mcaliley@gmail.com" + s.files = `git ls-files`.split("\n") + s.homepage = "https://github.com/charlotte-ruby/impressionist" + s.licenses = ["MIT"] + s.name = "impressionist" + s.require_paths = ["lib"] + s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version= + s.summary = "Easy way to log impressions" + s.test_files = `git ls-files -- test_app/*`.split("\n") + s.version = Impressionist::VERSION +end diff --git a/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb b/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb new file mode 100644 index 00000000..31f4c1a4 --- /dev/null +++ b/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module Generators + class ImpressionistGenerator < Rails::Generators::Base + include Rails::Generators::Migration + source_root File.join(File.dirname(__FILE__), 'templates') + + def self.next_migration_number(dirname) + sleep 1 + if ActiveRecord::Base.timestamped_migrations + Time.now.utc.strftime("%Y%m%d%H%M%S") + else + "%.3d" % (current_migration_number(dirname) + 1) + end + end + + def create_migration_file + migration_template 'create_impressions_table.rb', 'db/migrate/create_impressions_table.rb' + end + end + end +end diff --git a/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb b/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb new file mode 100644 index 00000000..5282380c --- /dev/null +++ b/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb @@ -0,0 +1,30 @@ +class CreateImpressionsTable < ActiveRecord::Migration + def self.up + create_table :impressions, :force => true do |t| + t.string :impressionable_type + t.integer :impressionable_id + t.integer :user_id + t.string :controller_name + t.string :action_name + t.string :view_name + t.string :request_hash + t.string :ip_address + t.string :session_hash + t.text :message + t.text :referrer + t.timestamps + end + add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false + add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false + add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false + add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false + add_index :impressions, :user_id + end + + def self.down + drop_table :impressions + end +end diff --git a/vendor/impressionist/lib/generators/impressionist_generator.rb b/vendor/impressionist/lib/generators/impressionist_generator.rb new file mode 100644 index 00000000..30290af2 --- /dev/null +++ b/vendor/impressionist/lib/generators/impressionist_generator.rb @@ -0,0 +1,12 @@ +module Impressionist + module Generators + class ImpressionistGenerator < Rails::Generators::Base + hook_for :orm + source_root File.expand_path('../templates', __FILE__) + + def copy_config_file + template 'impression.rb', 'config/initializers/impression.rb' + end + end + end +end diff --git a/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb b/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb new file mode 100644 index 00000000..eaa1b65c --- /dev/null +++ b/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb @@ -0,0 +1,8 @@ +module MongoMapper + module Generators + class ImpressionistGenerator < Rails::Generators::Base + # Empty for now, need it for generating the config file without + # triggering other ORM's generators. + end + end +end diff --git a/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb b/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb new file mode 100644 index 00000000..e56bf904 --- /dev/null +++ b/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb @@ -0,0 +1,8 @@ +module Mongoid + module Generators + class ImpressionistGenerator < Rails::Generators::Base + # Empty for now, need it for generating the config file without + # triggering other ORM's generators. + end + end +end diff --git a/vendor/impressionist/lib/generators/templates/impression.rb b/vendor/impressionist/lib/generators/templates/impression.rb new file mode 100644 index 00000000..fbdee915 --- /dev/null +++ b/vendor/impressionist/lib/generators/templates/impression.rb @@ -0,0 +1,5 @@ +# Use this hook to configure impressionist parameters +Impressionist.setup do |config| + # Define ORM. Could be :active_record (default) and :mongo_mapper + # config.orm = :active_record +end diff --git a/vendor/impressionist/lib/impressionist.rb b/vendor/impressionist/lib/impressionist.rb new file mode 100644 index 00000000..7ead8fe9 --- /dev/null +++ b/vendor/impressionist/lib/impressionist.rb @@ -0,0 +1,12 @@ +require "impressionist/engine.rb" + +module Impressionist + # Define ORM + mattr_accessor :orm + @@orm = :active_record + + # Load configuration from initializer + def self.setup + yield self + end +end diff --git a/vendor/impressionist/lib/impressionist/bots.rb b/vendor/impressionist/lib/impressionist/bots.rb new file mode 100644 index 00000000..10d3fd64 --- /dev/null +++ b/vendor/impressionist/lib/impressionist/bots.rb @@ -0,0 +1,18 @@ +require 'httpclient' +require 'nokogiri' + +module Impressionist + module Bots + LIST_URL = "http://www.user-agents.org/allagents.xml" + def self.consume + response = HTTPClient.new.get_content(LIST_URL) + doc = Nokogiri::XML(response) + list = [] + doc.xpath('//user-agent').each do |agent| + type = agent.xpath("Type").text + list << agent.xpath("String").text.gsub("<","<") if ["R","S"].include?(type) #gsub hack for badly formatted data + end + list + end + end +end diff --git a/vendor/impressionist/lib/impressionist/engine.rb b/vendor/impressionist/lib/impressionist/engine.rb new file mode 100644 index 00000000..8f953d75 --- /dev/null +++ b/vendor/impressionist/lib/impressionist/engine.rb @@ -0,0 +1,30 @@ +require "impressionist" +require "rails" + +module Impressionist + class Engine < Rails::Engine + initializer 'impressionist.model' do |app| + require "#{root}/app/models/impressionist/impressionable.rb" + if Impressionist.orm == :active_record + require "impressionist/models/active_record/impression.rb" + require "impressionist/models/active_record/impressionist/impressionable.rb" + ActiveRecord::Base.send(:include, Impressionist::Impressionable) + elsif Impressionist.orm == :mongo_mapper + require "impressionist/models/mongo_mapper/impression.rb" + require "impressionist/models/mongo_mapper/impressionist/impressionable.rb" + MongoMapper::Document.plugin Impressionist::Impressionable + elsif Impressionist.orm == :mongoid + require "impressionist/models/mongoid/impression.rb" + require "impressionist/models/mongoid/impressionist/impressionable.rb" + # Mongoid::Document Impressionist::Impressionable + end + end + + initializer 'impressionist.controller' do + ActiveSupport.on_load(:action_controller) do + include ImpressionistController::InstanceMethods + extend ImpressionistController::ClassMethods + end + end + end +end diff --git a/vendor/impressionist/lib/impressionist/models/active_record/impression.rb b/vendor/impressionist/lib/impressionist/models/active_record/impression.rb new file mode 100644 index 00000000..94c2710c --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/active_record/impression.rb @@ -0,0 +1,18 @@ +class Impression < ActiveRecord::Base + attr_accessible :impressionable_type, :impressionable_id, :user_id, + :controller_name, :action_name, :view_name, :request_hash, :ip_address, + :session_hash, :message, :referrer + + after_save :update_impressions_counter_cache + + private + + def update_impressions_counter_cache + impressionable_class = self.impressionable_type.constantize + + if impressionable_class.impressionist_counter_cache_options + resouce = impressionable_class.find(self.impressionable_id) + resouce.try(:update_impressionist_counter_cache) + end + end +end diff --git a/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb new file mode 100644 index 00000000..6100b886 --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb @@ -0,0 +1,12 @@ +module Impressionist + module Impressionable + extend ActiveSupport::Concern + + module ClassMethods + def is_impressionable(options={}) + has_many :impressions, :as => :impressionable, :dependent => :destroy + @impressionist_cache_options = options[:counter_cache] + end + end + end +end diff --git a/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb new file mode 100644 index 00000000..a7d5f64c --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb @@ -0,0 +1,16 @@ +class Impression + include MongoMapper::Document + + key :impressionable_type, String + key :impressionable_id, String + key :user_id, String + key :controller_name, String + key :action_name, String + key :view_name, String + key :request_hash, String + key :ip_address, String + key :session_hash, String + key :message, String + key :referrer, String + timestamps! +end diff --git a/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb new file mode 100644 index 00000000..9e0dd524 --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb @@ -0,0 +1,12 @@ +module Impressionist + module Impressionable + extend ActiveSupport::Concern + + module ClassMethods + def is_impressionable(options={}) + many :impressions, :as => :impressionable, :dependent => :destroy + @cache_options = options[:counter_cache] + end + end + end +end diff --git a/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb b/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb new file mode 100644 index 00000000..5edc2edd --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb @@ -0,0 +1,37 @@ +class Impression + include Mongoid::Document + include Mongoid::Timestamps + + field :impressionable_type + field :impressionable_id + field :user_id + field :controller_name + field :action_name + field :view_name + field :request_hash + field :ip_address + field :session_hash + field :message + field :referrer + + belongs_to :impressionable, :polymorphic => true + + after_save :update_impressions_counter_cache + + def self.impressionist_count(options={}) + options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now) + imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date]) + options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count + end + + private + + def update_impressions_counter_cache + impressionable_class = self.impressionable_type.constantize + + if impressionable_class.impressionist_counter_cache_options + resouce = impressionable_class.find(self.impressionable_id) + resouce.try(:update_impressionist_counter_cache) + end + end +end diff --git a/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb new file mode 100644 index 00000000..6100b886 --- /dev/null +++ b/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb @@ -0,0 +1,12 @@ +module Impressionist + module Impressionable + extend ActiveSupport::Concern + + module ClassMethods + def is_impressionable(options={}) + has_many :impressions, :as => :impressionable, :dependent => :destroy + @impressionist_cache_options = options[:counter_cache] + end + end + end +end diff --git a/vendor/impressionist/lib/impressionist/version.rb b/vendor/impressionist/lib/impressionist/version.rb new file mode 100644 index 00000000..cc644dcd --- /dev/null +++ b/vendor/impressionist/lib/impressionist/version.rb @@ -0,0 +1,3 @@ +module Impressionist + VERSION = "1.1.1" +end diff --git a/vendor/impressionist/logo.png b/vendor/impressionist/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9106036b03e6235342f75ed3e9a3d12ad0420bde GIT binary patch literal 63294 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 z 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 literal 0 HcmV?d00001 diff --git a/vendor/impressionist/test_app/.gitignore b/vendor/impressionist/test_app/.gitignore new file mode 100644 index 00000000..351f6382 --- /dev/null +++ b/vendor/impressionist/test_app/.gitignore @@ -0,0 +1,17 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +# Ignore bundler config +/.bundle +/Gemfile.lock + +# Ignore the default SQLite database. +/db/*.sqlite3 + +# Ignore all logfiles and tempfiles. +/coverage +/log/*.log +/tmp diff --git a/vendor/impressionist/test_app/.rspec b/vendor/impressionist/test_app/.rspec new file mode 100644 index 00000000..4e1e0d2f --- /dev/null +++ b/vendor/impressionist/test_app/.rspec @@ -0,0 +1 @@ +--color diff --git a/vendor/impressionist/test_app/Gemfile b/vendor/impressionist/test_app/Gemfile new file mode 100644 index 00000000..67f8bb24 --- /dev/null +++ b/vendor/impressionist/test_app/Gemfile @@ -0,0 +1,59 @@ +source 'https://rubygems.org' + +gem 'rails', '3.2.2' + +gem 'impressionist', :path => '../' + +platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' + gem 'jruby-openssl' +end + +platforms :ruby, :mswin, :mingw do + gem 'pg' + gem 'sqlite3' +end + +gem 'json' + +# Gems used only for assets and not required +# in production environments by default. +group :assets do + gem 'sass-rails', '~> 3.2.3' + gem 'coffee-rails', '~> 3.2.1' + + # See https://github.com/sstephenson/execjs#readme for more supported runtimes + # gem 'therubyracer' + + gem 'uglifier', '>= 1.0.3' +end + +group :development, :test do + gem 'autotest-notification' + gem 'rspec-rails' + gem 'spork' +end + +group :test do + gem 'capybara' + gem 'simplecov' + gem 'systemu' +end + +gem 'jquery-rails' + +# To use ActiveModel has_secure_password +# gem 'bcrypt-ruby', '~> 3.0.0' + +# To use Jbuilder templates for JSON +# gem 'jbuilder' + +# Use unicorn as the app server +# gem 'unicorn' + +# Deploy with Capistrano +# gem 'capistrano' + +# To use debugger +# gem 'ruby-debug' diff --git a/vendor/impressionist/test_app/README b/vendor/impressionist/test_app/README new file mode 100644 index 00000000..fe7013d5 --- /dev/null +++ b/vendor/impressionist/test_app/README @@ -0,0 +1,256 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +2. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +3. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +4. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands +running on the server.log and development.log. Rails will automatically display +debugging and runtime information to these files. Debugging info will also be +shown in the browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code +using the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are +several books available online as well: + +* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two books will bring you up to speed on the Ruby language and also on +programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your +Mongrel or WEBrick server with --debugger. This means that you can break out of +execution at any point in the code, investigate and change the model, and then, +resume execution! You need to install ruby-debug to run the server in debugging +mode. With gems, use sudo gem install ruby-debug. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, "body"=>nil, "id"=>"1"}>, + #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better, you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you can enter "cont". + + +== Console + +The console is a Ruby shell, which allows you to interact with your +application's domain model. Here you'll have all parts of the application +configured, just like it is when the application is running. You can inspect +domain models, change values, and save to the database. Starting the script +without arguments will launch it in the development environment. + +To start the console, run rails console from the application +directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications + made to the database. +* Passing an environment name as an argument will load the corresponding + environment. Example: rails console production. + +To reload your controllers and models after launching the console run +reload! + +More information about irb can be found at: +link:http://www.rubycentral.com/pickaxe/irb.html + + +== dbconsole + +You can go to the command line of your database directly through rails +dbconsole. You would be connected to the database with the credentials +defined in database.yml. Starting the script without arguments will connect you +to the development database. Passing an argument will connect you to a different +database, like rails dbconsole production. Currently works for MySQL, +PostgreSQL and SQLite 3. + +== Description of Contents + +The default directory structure of a generated Ruby on Rails application: + + |-- app + | |-- controllers + | |-- helpers + | |-- mailers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | `-- tasks + |-- log + |-- public + | |-- images + | |-- javascripts + | `-- stylesheets + |-- script + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | |-- cache + | |-- pids + | |-- sessions + | `-- sockets + `-- vendor + `-- plugins + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from + ApplicationController which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. Models descend from + ActiveRecord::Base by default. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use + eRuby syntax by default. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the + common header/footer method of wrapping views. In your views, define a layout + using the layout :default and create a file named default.html.erb. + Inside default.html.erb, call <% yield %> to render the view using this + layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are + generated for you automatically when using generators for controllers. + Helpers can be used to wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, + and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all the + sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when + generated using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that + doesn't belong under controllers, models, or helpers. This directory is in + the load path. + +public + The directory available for the web server. Contains subdirectories for + images, stylesheets, and javascripts. Also contains the dispatchers and the + default HTML files. This should be set as the DOCUMENT_ROOT of your web + server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate + command, template test files will be generated for you and placed in this + directory. + +vendor + External libraries that the application depends on. Also includes the plugins + subdirectory. If the app has frozen rails, those gems also go here, under + vendor/rails/. This directory is in the load path. diff --git a/vendor/impressionist/test_app/README.rdoc b/vendor/impressionist/test_app/README.rdoc new file mode 100644 index 00000000..7c36f235 --- /dev/null +++ b/vendor/impressionist/test_app/README.rdoc @@ -0,0 +1,261 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +2. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +3. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +4. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands +running on the server.log and development.log. Rails will automatically display +debugging and runtime information to these files. Debugging info will also be +shown in the browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code +using the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are +several books available online as well: + +* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two books will bring you up to speed on the Ruby language and also on +programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your +Mongrel or WEBrick server with --debugger. This means that you can break out of +execution at any point in the code, investigate and change the model, and then, +resume execution! You need to install ruby-debug to run the server in debugging +mode. With gems, use sudo gem install ruby-debug. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.all + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, "body"=>nil, "id"=>"1"}>, + #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better, you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you can enter "cont". + + +== Console + +The console is a Ruby shell, which allows you to interact with your +application's domain model. Here you'll have all parts of the application +configured, just like it is when the application is running. You can inspect +domain models, change values, and save to the database. Starting the script +without arguments will launch it in the development environment. + +To start the console, run rails console from the application +directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications + made to the database. +* Passing an environment name as an argument will load the corresponding + environment. Example: rails console production. + +To reload your controllers and models after launching the console run +reload! + +More information about irb can be found at: +link:http://www.rubycentral.org/pickaxe/irb.html + + +== dbconsole + +You can go to the command line of your database directly through rails +dbconsole. You would be connected to the database with the credentials +defined in database.yml. Starting the script without arguments will connect you +to the development database. Passing an argument will connect you to a different +database, like rails dbconsole production. Currently works for MySQL, +PostgreSQL and SQLite 3. + +== Description of Contents + +The default directory structure of a generated Ruby on Rails application: + + |-- app + | |-- assets + | |-- images + | |-- javascripts + | `-- stylesheets + | |-- controllers + | |-- helpers + | |-- mailers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | `-- tasks + |-- log + |-- public + |-- script + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | |-- cache + | |-- pids + | |-- sessions + | `-- sockets + `-- vendor + |-- assets + `-- stylesheets + `-- plugins + +app + Holds all the code that's specific to this particular application. + +app/assets + Contains subdirectories for images, stylesheets, and JavaScript files. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from + ApplicationController which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. Models descend from + ActiveRecord::Base by default. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use + eRuby syntax by default. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the + common header/footer method of wrapping views. In your views, define a layout + using the layout :default and create a file named default.html.erb. + Inside default.html.erb, call <% yield %> to render the view using this + layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are + generated for you automatically when using generators for controllers. + Helpers can be used to wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, + and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all the + sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when + generated using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that + doesn't belong under controllers, models, or helpers. This directory is in + the load path. + +public + The directory available for the web server. Also contains the dispatchers and the + default HTML files. This should be set as the DOCUMENT_ROOT of your web + server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate + command, template test files will be generated for you and placed in this + directory. + +vendor + External libraries that the application depends on. Also includes the plugins + subdirectory. If the app has frozen rails, those gems also go here, under + vendor/rails/. This directory is in the load path. diff --git a/vendor/impressionist/test_app/Rakefile b/vendor/impressionist/test_app/Rakefile new file mode 100644 index 00000000..9946cea6 --- /dev/null +++ b/vendor/impressionist/test_app/Rakefile @@ -0,0 +1,7 @@ +#!/usr/bin/env rake +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +TestApp::Application.load_tasks diff --git a/vendor/impressionist/test_app/app/assets/images/rails.png b/vendor/impressionist/test_app/app/assets/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..d5edc04e65f555e3ba4dcdaad39dc352e75b575e GIT binary patch literal 6646 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/vendor/impressionist/test_app/app/assets/javascripts/application.js b/vendor/impressionist/test_app/app/assets/javascripts/application.js new file mode 100644 index 00000000..9097d830 --- /dev/null +++ b/vendor/impressionist/test_app/app/assets/javascripts/application.js @@ -0,0 +1,15 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require jquery +//= require jquery_ujs +//= require_tree . diff --git a/vendor/impressionist/test_app/app/assets/stylesheets/application.css b/vendor/impressionist/test_app/app/assets/stylesheets/application.css new file mode 100644 index 00000000..3b5cc664 --- /dev/null +++ b/vendor/impressionist/test_app/app/assets/stylesheets/application.css @@ -0,0 +1,13 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the top of the + * compiled file, but it's generally better to create a new file per style scope. + * + *= require_self + *= require_tree . +*/ diff --git a/vendor/impressionist/test_app/app/controllers/application_controller.rb b/vendor/impressionist/test_app/app/controllers/application_controller.rb new file mode 100644 index 00000000..1ce61fa1 --- /dev/null +++ b/vendor/impressionist/test_app/app/controllers/application_controller.rb @@ -0,0 +1,8 @@ +class ApplicationController < ActionController::Base + protect_from_forgery + before_filter :secondary_before_filter + + def secondary_before_filter + @test_secondary_before_filter = "this is a test" + end +end diff --git a/vendor/impressionist/test_app/app/controllers/articles_controller.rb b/vendor/impressionist/test_app/app/controllers/articles_controller.rb new file mode 100644 index 00000000..367b460f --- /dev/null +++ b/vendor/impressionist/test_app/app/controllers/articles_controller.rb @@ -0,0 +1,18 @@ +class ArticlesController < ApplicationController + before_filter :test_current_user_var + + def test_current_user_var + if session[:user_id] + @current_user = User.new + @current_user.id = session[:user_id] + end + end + + def index + impressionist(Article.first,"this is a test article impression") + end + + def show + impressionist(Article.first) + end +end diff --git a/vendor/impressionist/test_app/app/controllers/dummy_controller.rb b/vendor/impressionist/test_app/app/controllers/dummy_controller.rb new file mode 100644 index 00000000..86664a67 --- /dev/null +++ b/vendor/impressionist/test_app/app/controllers/dummy_controller.rb @@ -0,0 +1,6 @@ +# This controller imports the impressionist module to make the modules methods available for testing +class DummyController < ActionController::Base + + impressionist + +end diff --git a/vendor/impressionist/test_app/app/controllers/posts_controller.rb b/vendor/impressionist/test_app/app/controllers/posts_controller.rb new file mode 100644 index 00000000..35262709 --- /dev/null +++ b/vendor/impressionist/test_app/app/controllers/posts_controller.rb @@ -0,0 +1,23 @@ +class PostsController < ApplicationController + helper_method :current_user + impressionist + def index + + end + + def show + + end + + def edit + + end + + def current_user + if session[:user_id] + user = User.new + user.id = session[:user_id] + @current_user ||= user + end + end +end diff --git a/vendor/impressionist/test_app/app/controllers/widgets_controller.rb b/vendor/impressionist/test_app/app/controllers/widgets_controller.rb new file mode 100644 index 00000000..b6b99a72 --- /dev/null +++ b/vendor/impressionist/test_app/app/controllers/widgets_controller.rb @@ -0,0 +1,12 @@ +class WidgetsController < ApplicationController + impressionist :actions=>[:show,:index], :unique => [:controller_name,:action_name,:impressionable_id] + + def show + end + + def index + end + + def new + end +end diff --git a/vendor/impressionist/test_app/app/helpers/application_helper.rb b/vendor/impressionist/test_app/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/vendor/impressionist/test_app/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/vendor/impressionist/test_app/app/mailers/.gitkeep b/vendor/impressionist/test_app/app/mailers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/models/.gitkeep b/vendor/impressionist/test_app/app/models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/models/article.rb b/vendor/impressionist/test_app/app/models/article.rb new file mode 100644 index 00000000..0a18546d --- /dev/null +++ b/vendor/impressionist/test_app/app/models/article.rb @@ -0,0 +1,3 @@ +class Article < ActiveRecord::Base + is_impressionable +end diff --git a/vendor/impressionist/test_app/app/models/dummy.rb b/vendor/impressionist/test_app/app/models/dummy.rb new file mode 100644 index 00000000..745c60aa --- /dev/null +++ b/vendor/impressionist/test_app/app/models/dummy.rb @@ -0,0 +1,7 @@ +# We don't really care about this model. It's just being used to test the uniqueness controller +# specs. Nevertheless, we need a model because the counter caching functionality expects it. +# +class Dummy < ActiveRecord::Base + self.abstract_class = true # doesn't need to be backed by an actual table + is_impressionable +end diff --git a/vendor/impressionist/test_app/app/models/post.rb b/vendor/impressionist/test_app/app/models/post.rb new file mode 100644 index 00000000..ccc43dac --- /dev/null +++ b/vendor/impressionist/test_app/app/models/post.rb @@ -0,0 +1,3 @@ +class Post < ActiveRecord::Base + is_impressionable +end diff --git a/vendor/impressionist/test_app/app/models/user.rb b/vendor/impressionist/test_app/app/models/user.rb new file mode 100644 index 00000000..6da0f650 --- /dev/null +++ b/vendor/impressionist/test_app/app/models/user.rb @@ -0,0 +1,3 @@ +class User + attr_accessor :id +end diff --git a/vendor/impressionist/test_app/app/models/widget.rb b/vendor/impressionist/test_app/app/models/widget.rb new file mode 100644 index 00000000..51bab18d --- /dev/null +++ b/vendor/impressionist/test_app/app/models/widget.rb @@ -0,0 +1,3 @@ +class Widget < ActiveRecord::Base + is_impressionable :counter_cache => true +end diff --git a/vendor/impressionist/test_app/app/views/articles/index.html.erb b/vendor/impressionist/test_app/app/views/articles/index.html.erb new file mode 100644 index 00000000..97e10f82 --- /dev/null +++ b/vendor/impressionist/test_app/app/views/articles/index.html.erb @@ -0,0 +1 @@ +<%=@impressionist_hash==nil%> diff --git a/vendor/impressionist/test_app/app/views/articles/show.html.erb b/vendor/impressionist/test_app/app/views/articles/show.html.erb new file mode 100644 index 00000000..7dc920f3 --- /dev/null +++ b/vendor/impressionist/test_app/app/views/articles/show.html.erb @@ -0,0 +1 @@ +<%=link_to "Same Page", article_url(Article.first)%> diff --git a/vendor/impressionist/test_app/app/views/layouts/application.html.erb b/vendor/impressionist/test_app/app/views/layouts/application.html.erb new file mode 100644 index 00000000..d4557d87 --- /dev/null +++ b/vendor/impressionist/test_app/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + TestApp + <%= stylesheet_link_tag "application", :media => "all" %> + <%= javascript_include_tag "application" %> + <%= csrf_meta_tags %> + + + +<%= yield %> + + + diff --git a/vendor/impressionist/test_app/app/views/posts/edit.html.erb b/vendor/impressionist/test_app/app/views/posts/edit.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/views/posts/index.html.erb b/vendor/impressionist/test_app/app/views/posts/index.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/views/posts/show.html.erb b/vendor/impressionist/test_app/app/views/posts/show.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/views/widgets/index.html.erb b/vendor/impressionist/test_app/app/views/widgets/index.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/views/widgets/new.html.erb b/vendor/impressionist/test_app/app/views/widgets/new.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/app/views/widgets/show.html.erb b/vendor/impressionist/test_app/app/views/widgets/show.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/config.ru b/vendor/impressionist/test_app/config.ru new file mode 100644 index 00000000..86a587d0 --- /dev/null +++ b/vendor/impressionist/test_app/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run TestApp::Application diff --git a/vendor/impressionist/test_app/config/application.rb b/vendor/impressionist/test_app/config/application.rb new file mode 100644 index 00000000..d0d0095d --- /dev/null +++ b/vendor/impressionist/test_app/config/application.rb @@ -0,0 +1,59 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +if defined?(Bundler) + # If you precompile assets before deploying to production, use this line + Bundler.require(*Rails.groups(:assets => %w(development test))) + # If you want your assets lazily compiled in production, use this line + # Bundler.require(:default, :assets, Rails.env) +end + +module TestApp + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + + # Use SQL instead of Active Record's schema dumper when creating the database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Enforce whitelist mode for mass assignment. + # This will create an empty whitelist of attributes available for mass-assignment for all models + # in your app. As such, your models will need to explicitly whitelist or blacklist accessible + # parameters by using an attr_accessible or attr_protected declaration. + # config.active_record.whitelist_attributes = true + + # Enable the asset pipeline + config.assets.enabled = true + + # Version of your assets, change this if you want to expire all your assets + config.assets.version = '1.0' + end +end diff --git a/vendor/impressionist/test_app/config/boot.rb b/vendor/impressionist/test_app/config/boot.rb new file mode 100644 index 00000000..4489e586 --- /dev/null +++ b/vendor/impressionist/test_app/config/boot.rb @@ -0,0 +1,6 @@ +require 'rubygems' + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/vendor/impressionist/test_app/config/cucumber.yml b/vendor/impressionist/test_app/config/cucumber.yml new file mode 100644 index 00000000..6f304dfa --- /dev/null +++ b/vendor/impressionist/test_app/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip" +%> +default: --drb <%= std_opts %> features +wip: --drb --tags @wip:3 --wip features +rerun: --drb <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/vendor/impressionist/test_app/config/database.yml b/vendor/impressionist/test_app/config/database.yml new file mode 100644 index 00000000..45fed06f --- /dev/null +++ b/vendor/impressionist/test_app/config/database.yml @@ -0,0 +1,30 @@ +# SQLite version 3.x +# gem install sqlite3-ruby (not necessary on OS X Leopard) +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +test: &test + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 + +#pg_test: +# adapter: postgresql +# database: impressionist_test +# username: johnmcaliley +# password: +# host: localhost +# encoding: UTF8 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + pool: 5 + timeout: 5000 + +cucumber: + <<: *test diff --git a/vendor/impressionist/test_app/config/environment.rb b/vendor/impressionist/test_app/config/environment.rb new file mode 100644 index 00000000..f4cc1e44 --- /dev/null +++ b/vendor/impressionist/test_app/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +TestApp::Application.initialize! diff --git a/vendor/impressionist/test_app/config/environments/development.rb b/vendor/impressionist/test_app/config/environments/development.rb new file mode 100644 index 00000000..b5306b32 --- /dev/null +++ b/vendor/impressionist/test_app/config/environments/development.rb @@ -0,0 +1,37 @@ +TestApp::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin + + # Raise exception on mass assignment protection for Active Record models + config.active_record.mass_assignment_sanitizer = :strict + + # Log the query plan for queries taking more than this (works + # with SQLite, MySQL, and PostgreSQL) + config.active_record.auto_explain_threshold_in_seconds = 0.5 + + # Do not compress assets + config.assets.compress = false + + # Expands the lines which load the assets + config.assets.debug = true +end diff --git a/vendor/impressionist/test_app/config/environments/pg_test.rb b/vendor/impressionist/test_app/config/environments/pg_test.rb new file mode 100644 index 00000000..d17c1aeb --- /dev/null +++ b/vendor/impressionist/test_app/config/environments/pg_test.rb @@ -0,0 +1,35 @@ +TestApp::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/vendor/impressionist/test_app/config/environments/production.rb b/vendor/impressionist/test_app/config/environments/production.rb new file mode 100644 index 00000000..c33789b2 --- /dev/null +++ b/vendor/impressionist/test_app/config/environments/production.rb @@ -0,0 +1,67 @@ +TestApp::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Disable Rails's static asset server (Apache or nginx will already do this) + config.serve_static_assets = false + + # Compress JavaScripts and CSS + config.assets.compress = true + + # Don't fallback to assets pipeline if a precompiled asset is missed + config.assets.compile = false + + # Generate digests for assets URLs + config.assets.digest = true + + # Defaults to Rails.root.join("public/assets") + # config.assets.manifest = YOUR_PATH + + # Specifies the header that your server uses for sending files + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Prepend all log lines with the following tags + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # config.assets.precompile += %w( search.js ) + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify + + # Log the query plan for queries taking more than this (works + # with SQLite, MySQL, and PostgreSQL) + # config.active_record.auto_explain_threshold_in_seconds = 0.5 +end diff --git a/vendor/impressionist/test_app/config/environments/test.rb b/vendor/impressionist/test_app/config/environments/test.rb new file mode 100644 index 00000000..21ec8e47 --- /dev/null +++ b/vendor/impressionist/test_app/config/environments/test.rb @@ -0,0 +1,37 @@ +TestApp::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Configure static asset server for tests with Cache-Control for performance + config.serve_static_assets = true + config.static_cache_control = "public, max-age=3600" + + # Log error messages when you accidentally call methods on nil + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Raise exception on mass assignment protection for Active Record models + config.active_record.mass_assignment_sanitizer = :strict + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb b/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000..59385cdf --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/vendor/impressionist/test_app/config/initializers/impression.rb b/vendor/impressionist/test_app/config/initializers/impression.rb new file mode 100644 index 00000000..fbdee915 --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/impression.rb @@ -0,0 +1,5 @@ +# Use this hook to configure impressionist parameters +Impressionist.setup do |config| + # Define ORM. Could be :active_record (default) and :mongo_mapper + # config.orm = :active_record +end diff --git a/vendor/impressionist/test_app/config/initializers/inflections.rb b/vendor/impressionist/test_app/config/initializers/inflections.rb new file mode 100644 index 00000000..5d8d9be2 --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/inflections.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end +# +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/vendor/impressionist/test_app/config/initializers/mime_types.rb b/vendor/impressionist/test_app/config/initializers/mime_types.rb new file mode 100644 index 00000000..72aca7e4 --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/vendor/impressionist/test_app/config/initializers/secret_token.rb b/vendor/impressionist/test_app/config/initializers/secret_token.rb new file mode 100644 index 00000000..30f90d45 --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +TestApp::Application.config.secret_token = '4a6fd2eb397985331d209be32073259ed7c25aef4fafcabb00e483ee548e592322277eb15459bdb257b65f31146eda92684b3e7a98ea1b2dfad9b0d08ab62e10' diff --git a/vendor/impressionist/test_app/config/initializers/session_store.rb b/vendor/impressionist/test_app/config/initializers/session_store.rb new file mode 100644 index 00000000..8188ba2d --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/session_store.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +TestApp::Application.config.session_store :cookie_store, :key => '_test_app_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# TestApp::Application.config.session_store :active_record_store diff --git a/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb b/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb new file mode 100644 index 00000000..da4fb076 --- /dev/null +++ b/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters :format => [:json] +end + +# Disable root element in JSON by default. +ActiveSupport.on_load(:active_record) do + self.include_root_in_json = false +end diff --git a/vendor/impressionist/test_app/config/locales/en.yml b/vendor/impressionist/test_app/config/locales/en.yml new file mode 100644 index 00000000..179c14ca --- /dev/null +++ b/vendor/impressionist/test_app/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/vendor/impressionist/test_app/config/routes.rb b/vendor/impressionist/test_app/config/routes.rb new file mode 100644 index 00000000..7bcb78ea --- /dev/null +++ b/vendor/impressionist/test_app/config/routes.rb @@ -0,0 +1,3 @@ +TestApp::Application.routes.draw do + resources :articles, :posts, :widgets +end diff --git a/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb b/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb new file mode 100644 index 00000000..66f46ef4 --- /dev/null +++ b/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb @@ -0,0 +1,13 @@ +class CreateArticles < ActiveRecord::Migration + def self.up + create_table :articles do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :articles + end +end diff --git a/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb b/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb new file mode 100644 index 00000000..f018e1ce --- /dev/null +++ b/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb @@ -0,0 +1,13 @@ +class CreatePosts < ActiveRecord::Migration + def self.up + create_table :posts do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :posts + end +end diff --git a/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb b/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb new file mode 100644 index 00000000..5cb1fe2c --- /dev/null +++ b/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb @@ -0,0 +1,15 @@ +class CreateWidgets < ActiveRecord::Migration + def self.up + create_table :widgets do |t| + t.string :name + t.integer :impressions_count + + t.timestamps + end + end + + def self.down + drop_table :widgets + end +end + diff --git a/vendor/impressionist/test_app/db/seeds.rb b/vendor/impressionist/test_app/db/seeds.rb new file mode 100644 index 00000000..d34dfa02 --- /dev/null +++ b/vendor/impressionist/test_app/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Mayor.create(:name => 'Emanuel', :city => cities.first) diff --git a/vendor/impressionist/test_app/lib/assets/.gitkeep b/vendor/impressionist/test_app/lib/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/lib/tasks/.gitkeep b/vendor/impressionist/test_app/lib/tasks/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/lib/tasks/cucumber.rake b/vendor/impressionist/test_app/lib/tasks/cucumber.rake new file mode 100644 index 00000000..982054e2 --- /dev/null +++ b/vendor/impressionist/test_app/lib/tasks/cucumber.rake @@ -0,0 +1,53 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/vendor/impressionist/test_app/log/.gitkeep b/vendor/impressionist/test_app/log/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/public/404.html b/vendor/impressionist/test_app/public/404.html new file mode 100644 index 00000000..9a48320a --- /dev/null +++ b/vendor/impressionist/test_app/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + diff --git a/vendor/impressionist/test_app/public/422.html b/vendor/impressionist/test_app/public/422.html new file mode 100644 index 00000000..83660ab1 --- /dev/null +++ b/vendor/impressionist/test_app/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + diff --git a/vendor/impressionist/test_app/public/500.html b/vendor/impressionist/test_app/public/500.html new file mode 100644 index 00000000..f3648a0d --- /dev/null +++ b/vendor/impressionist/test_app/public/500.html @@ -0,0 +1,25 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+
+ + diff --git a/vendor/impressionist/test_app/public/favicon.ico b/vendor/impressionist/test_app/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/public/images/rails.png b/vendor/impressionist/test_app/public/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..d5edc04e65f555e3ba4dcdaad39dc352e75b575e GIT binary patch literal 6646 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/vendor/impressionist/test_app/public/index.html b/vendor/impressionist/test_app/public/index.html new file mode 100644 index 00000000..a1d50995 --- /dev/null +++ b/vendor/impressionist/test_app/public/index.html @@ -0,0 +1,241 @@ + + + + Ruby on Rails: Welcome aboard + + + + +
+ + +
+ + + + +
+

Getting started

+

Here’s how to get rolling:

+ +
    +
  1. +

    Use rails generate to create your models and controllers

    +

    To see all available options, run it without parameters.

    +
  2. + +
  3. +

    Set up a default route and remove public/index.html

    +

    Routes are set up in config/routes.rb.

    +
  4. + +
  5. +

    Create your database

    +

    Run rake db:create to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    +
  6. +
+
+
+ + +
+ + diff --git a/vendor/impressionist/test_app/public/javascripts/application.js b/vendor/impressionist/test_app/public/javascripts/application.js new file mode 100644 index 00000000..fe457769 --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/vendor/impressionist/test_app/public/javascripts/controls.js b/vendor/impressionist/test_app/public/javascripts/controls.js new file mode 100644 index 00000000..75d1bed7 --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/controls.js @@ -0,0 +1,965 @@ +// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// 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/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/vendor/impressionist/test_app/public/javascripts/dragdrop.js b/vendor/impressionist/test_app/public/javascripts/dragdrop.js new file mode 100644 index 00000000..579715ec --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/dragdrop.js @@ -0,0 +1,974 @@ +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// 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 = this.element.cumulativeOffset(); + 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._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._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._originallyAbsolute) + Position.relativize(this.element); + delete this._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 = this.element.cumulativeOffset(); + 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){ + element = $(element); + var s = Sortable.sortables[element.id]; + + 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.identify()] = 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 = dropon.cumulativeOffset(); + 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')]; +}; diff --git a/vendor/impressionist/test_app/public/javascripts/effects.js b/vendor/impressionist/test_app/public/javascripts/effects.js new file mode 100644 index 00000000..c977462f --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/effects.js @@ -0,0 +1,1123 @@ +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// 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/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect, options) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, options || {})); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/vendor/impressionist/test_app/public/javascripts/prototype.js b/vendor/impressionist/test_app/public/javascripts/prototype.js new file mode 100644 index 00000000..06249a6a --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/prototype.js @@ -0,0 +1,6001 @@ +/* Prototype JavaScript framework, version 1.7_rc2 + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + + Version: '1.7_rc2', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.indexOf(pattern, d) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if (readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + + +(function(global) { + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; +Element.cache = { }; + +function purgeElement(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property, maximumLength) { + element = $(element); + maximumLength = maximumLength || -1; + var elements = []; + + while (element = element[property]) { + if (element.nodeType == 1) + elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; + }, + + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + element = $(element); + if (Object.isString(selector)) + return Prototype.Selector.match(element, selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Prototype.Selector.find(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } + }, + + next: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } + }, + + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); + }, + + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, + valueL = 0, + element = forElement; + + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + + element = $(element); + + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return $(document.body); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return Element._returnOffset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if (element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + }, + + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; + } + + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } + + if (/\d/.test(value) && element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } + + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(width); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = getPixelValue(width); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return Object.isNumber(element.clientTop) ? element.clientTop : + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return Object.isNumber(element.clientBottom) ? element.clientBottom : + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return Object.isNumber(element.clientLeft) ? element.clientLeft : + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return Object.isNumber(element.clientRight) ? element.clientRight : + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; + }, + + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + inspect: function() { + return "#".interpolate(this); + }, + + toString: function() { + return "[#{left}, #{top}]".interpolate(this); + }, + + toArray: function() { + return [this.left, this.top]; + } + }); + + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } + + function measure(element, property) { + return $(element).getLayout().get(property); + } + + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.get('width'), + height: layout.get('height') + }; + } + + function getOffsetParent(element) { + if (isDetached(element)) return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); + + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + + var eOffset = element.viewportOffset(), + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); + } + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } + + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); + return element; + } + + var responders = registry.get(eventName); + if (!responders) return element; + + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/vendor/impressionist/test_app/public/javascripts/rails.js b/vendor/impressionist/test_app/public/javascripts/rails.js new file mode 100644 index 00000000..4283ed89 --- /dev/null +++ b/vendor/impressionist/test_app/public/javascripts/rails.js @@ -0,0 +1,175 @@ +(function() { + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + function isEventSupported(eventName) { + var el = document.createElement('div'); + eventName = 'on' + eventName; + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, 'return;'); + isSupported = typeof el[eventName] == 'function'; + } + el = null; + return isSupported; + } + + function isForm(element) { + return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' + } + + function isInput(element) { + if (Object.isElement(element)) { + var name = element.nodeName.toUpperCase() + return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' + } + else return false + } + + var submitBubbles = isEventSupported('submit'), + changeBubbles = isEventSupported('change') + + if (!submitBubbles || !changeBubbles) { + // augment the Event.Handler class to observe custom events when needed + Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( + function(init, element, eventName, selector, callback) { + init(element, eventName, selector, callback) + // is the handler being attached to an element that doesn't support this event? + if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || + (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { + // "submit" => "emulated:submit" + this.eventName = 'emulated:' + this.eventName + } + } + ) + } + + if (!submitBubbles) { + // discover forms on the page by observing focus events which always bubble + document.on('focusin', 'form', function(focusEvent, form) { + // special handler for the real "submit" event (one-time operation) + if (!form.retrieve('emulated:submit')) { + form.on('submit', function(submitEvent) { + var emulated = form.fire('emulated:submit', submitEvent, true) + // if custom event received preventDefault, cancel the real one too + if (emulated.returnValue === false) submitEvent.preventDefault() + }) + form.store('emulated:submit', true) + } + }) + } + + if (!changeBubbles) { + // discover form inputs on the page + document.on('focusin', 'input, select, texarea', function(focusEvent, input) { + // special handler for real "change" events + if (!input.retrieve('emulated:change')) { + input.on('change', function(changeEvent) { + input.fire('emulated:change', changeEvent, true) + }) + input.store('emulated:change', true) + } + }) + } + + function handleRemote(element) { + var method, url, params; + + var event = element.fire("ajax:before"); + if (event.stopped) return false; + + if (element.tagName.toLowerCase() === 'form') { + method = element.readAttribute('method') || 'post'; + url = element.readAttribute('action'); + params = element.serialize(); + } else { + method = element.readAttribute('data-method') || 'get'; + url = element.readAttribute('href'); + params = {}; + } + + new Ajax.Request(url, { + method: method, + parameters: params, + evalScripts: true, + + onComplete: function(request) { element.fire("ajax:complete", request); }, + onSuccess: function(request) { element.fire("ajax:success", request); }, + onFailure: function(request) { element.fire("ajax:failure", request); } + }); + + element.fire("ajax:after"); + } + + function handleMethod(element) { + var method = element.readAttribute('data-method'), + url = element.readAttribute('href'), + csrf_param = $$('meta[name=csrf-param]')[0], + csrf_token = $$('meta[name=csrf-token]')[0]; + + var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); + element.parentNode.insert(form); + + if (method !== 'post') { + var field = new Element('input', { type: 'hidden', name: '_method', value: method }); + form.insert(field); + } + + if (csrf_param) { + var param = csrf_param.readAttribute('content'), + token = csrf_token.readAttribute('content'), + field = new Element('input', { type: 'hidden', name: param, value: token }); + form.insert(field); + } + + form.submit(); + } + + + document.on("click", "*[data-confirm]", function(event, element) { + var message = element.readAttribute('data-confirm'); + if (!confirm(message)) event.stop(); + }); + + document.on("click", "a[data-remote]", function(event, element) { + if (event.stopped) return; + handleRemote(element); + event.stop(); + }); + + document.on("click", "a[data-method]", function(event, element) { + if (event.stopped) return; + handleMethod(element); + event.stop(); + }); + + document.on("submit", function(event) { + var element = event.findElement(), + message = element.readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + + var inputs = element.select("input[type=submit][data-disable-with]"); + inputs.each(function(input) { + input.disabled = true; + input.writeAttribute('data-original-value', input.value); + input.value = input.readAttribute('data-disable-with'); + }); + + var element = event.findElement("form[data-remote]"); + if (element) { + handleRemote(element); + event.stop(); + } + }); + + document.on("ajax:after", "form", function(event, element) { + var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); + inputs.each(function(input) { + input.value = input.readAttribute('data-original-value'); + input.removeAttribute('data-original-value'); + input.disabled = false; + }); + }); +})(); diff --git a/vendor/impressionist/test_app/public/robots.txt b/vendor/impressionist/test_app/public/robots.txt new file mode 100644 index 00000000..085187fa --- /dev/null +++ b/vendor/impressionist/test_app/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-Agent: * +# Disallow: / diff --git a/vendor/impressionist/test_app/public/stylesheets/.gitkeep b/vendor/impressionist/test_app/public/stylesheets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/impressionist/test_app/script/cucumber b/vendor/impressionist/test_app/script/cucumber new file mode 100755 index 00000000..7fa5c920 --- /dev/null +++ b/vendor/impressionist/test_app/script/cucumber @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/vendor/impressionist/test_app/script/rails b/vendor/impressionist/test_app/script/rails new file mode 100755 index 00000000..f8da2cff --- /dev/null +++ b/vendor/impressionist/test_app/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/vendor/impressionist/test_app/spec/controllers/controller_spec.rb b/vendor/impressionist/test_app/spec/controllers/controller_spec.rb new file mode 100644 index 00000000..a48b3a06 --- /dev/null +++ b/vendor/impressionist/test_app/spec/controllers/controller_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +describe ArticlesController do + fixtures :articles,:impressions,:posts,:widgets + render_views + + it "should make the impressionable_hash available" do + get "index" + response.body.include?("false").should eq true + end + + it "should log an impression with a message" do + get "index" + Impression.all.size.should eq 12 + Article.first.impressions.last.message.should eq "this is a test article impression" + Article.first.impressions.last.controller_name.should eq "articles" + Article.first.impressions.last.action_name.should eq "index" + end + + it "should log an impression without a message" do + get "show", :id=> 1 + Impression.all.size.should eq 12 + Article.first.impressions.last.message.should eq nil + Article.first.impressions.last.controller_name.should eq "articles" + Article.first.impressions.last.action_name.should eq "show" + end + + it "should log the user_id if user is authenticated (@current_user before_filter method)" do + session[:user_id] = 123 + get "show", :id=> 1 + Article.first.impressions.last.user_id.should eq 123 + end + + it "should not log the user_id if user is authenticated" do + get "show", :id=> 1 + Article.first.impressions.last.user_id.should eq nil + end + + it "should log the request_hash, ip_address, referrer and session_hash" do + get "show", :id=> 1 + Impression.last.request_hash.size.should eq 64 + Impression.last.ip_address.should eq "0.0.0.0" + Impression.last.session_hash.size.should eq 32 + Impression.last.referrer.should eq nil + end + + it "should log the referrer when you click a link" do + visit article_url(Article.first) + click_link "Same Page" + Impression.last.referrer.should eq "http://test.host/articles/1" + end +end + +describe PostsController do + it "should log impression at the action level" do + get "show", :id=> 1 + Impression.all.size.should eq 12 + Impression.last.controller_name.should eq "posts" + Impression.last.action_name.should eq "show" + Impression.last.impressionable_type.should eq "Post" + Impression.last.impressionable_id.should eq 1 + end + + it "should log the user_id if user is authenticated (current_user helper method)" do + session[:user_id] = 123 + get "show", :id=> 1 + Post.first.impressions.last.user_id.should eq 123 + end +end + +describe WidgetsController do + + before(:each) do + @widget = Widget.find(1) + Widget.stub(:find).and_return(@widget) + end + + it "should log impression at the per action level" do + get "show", :id=> 1 + Impression.all.size.should eq 12 + get "index" + Impression.all.size.should eq 13 + get "new" + Impression.all.size.should eq 13 + end + + it "should not log impression when user-agent is in wildcard list" do + request.stub!(:user_agent).and_return('somebot') + get "show", :id=> 1 + Impression.all.size.should eq 11 + end + + it "should not log impression when user-agent is in the bot list" do + request.stub!(:user_agent).and_return('Acoon Robot v1.50.001') + get "show", :id=> 1 + Impression.all.size.should eq 11 + end + + describe "impressionist unique options" do + + it "should log unique impressions at the per action level" do + get "show", :id=> 1 + Impression.all.size.should eq 12 + get "show", :id=> 2 + Impression.all.size.should eq 13 + get "show", :id => 2 + Impression.all.size.should eq 13 + get "index" + Impression.all.size.should eq 14 + end + + it "should log unique impressions only once per id" do + get "show", :id=> 1 + Impression.all.size.should eq 12 + get "show", :id=> 2 + Impression.all.size.should eq 13 + get "show", :id => 2 + Impression.all.size.should eq 13 + get "index" + Impression.all.size.should eq 14 + end + + end + +end diff --git a/vendor/impressionist/test_app/spec/controllers/impressionist_uniqueness_spec.rb b/vendor/impressionist/test_app/spec/controllers/impressionist_uniqueness_spec.rb new file mode 100644 index 00000000..f9c3fc3e --- /dev/null +++ b/vendor/impressionist/test_app/spec/controllers/impressionist_uniqueness_spec.rb @@ -0,0 +1,310 @@ +require 'spec_helper' + +# we use the posts controller as it uses the impressionsist module. any such controller would do. +describe DummyController do + + before do + @impression_count = Impression.all.size + end + + describe "impressionist filter uniqueness" do + + it "should ignore uniqueness if not requested" do + controller.impressionist_subapp_filter(nil, nil) + controller.impressionist_subapp_filter(nil, nil) + Impression.should have(@impression_count + 2).records + end + + it "should recognize unique session" do + controller.stub!(:session_hash).and_return(request.session_options[:id]) + controller.impressionist_subapp_filter(nil, [:session_hash]) + controller.impressionist_subapp_filter(nil, [:session_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique ip" do + controller.request.stub!(:remote_ip).and_return("1.2.3.4") + controller.impressionist_subapp_filter(nil, [:ip_address]) + controller.impressionist_subapp_filter(nil, [:ip_address]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique request" do + controller.impressionist_subapp_filter(nil, [:request_hash]) + controller.impressionist_subapp_filter(nil, [:request_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique action" do + controller.stub!(:action_name).and_return("test_action") + controller.impressionist_subapp_filter(nil, [:action_name]) + controller.impressionist_subapp_filter(nil, [:action_name]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique controller" do + controller.stub!(:controller_name).and_return("post") + controller.impressionist_subapp_filter(nil, [:controller_name]) + controller.impressionist_subapp_filter(nil, [:controller_name]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique user" do + controller.stub!(:user_id).and_return(42) + controller.impressionist_subapp_filter(nil, [:user_id]) + controller.impressionist_subapp_filter(nil, [:user_id]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique referer" do + controller.request.stub!(:referer).and_return("http://foo/bar") + controller.impressionist_subapp_filter(nil, [:referrer]) + controller.impressionist_subapp_filter(nil, [:referrer]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique id" do + controller.stub!(:params).and_return({:id => "666"}) # for correct impressionable id in filter + controller.impressionist_subapp_filter(nil, [:impressionable_id]) + controller.impressionist_subapp_filter(nil, [:impressionable_id]) + Impression.should have(@impression_count + 1).records + end + + # extra redundant test for important controller and action combination. + it "should recognize different controller and action" do + controller.stub!(:controller_name).and_return("post") + controller.stub!(:action_name).and_return("test_action") + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + Impression.should have(@impression_count + 1).records + controller.stub!(:action_name).and_return("another_action") + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + Impression.should have(@impression_count + 2).records + controller.stub!(:controller_name).and_return("article") + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + controller.impressionist_subapp_filter(nil, [:controller_name, :action_name]) + Impression.should have(@impression_count + 3).records + end + + it "should recognize different action" do + controller.stub!(:action_name).and_return("test_action") + controller.impressionist_subapp_filter(nil, [:action_name]) + controller.impressionist_subapp_filter(nil, [:action_name]) + Impression.should have(@impression_count + 1).records + controller.stub!(:action_name).and_return("another_action") + controller.impressionist_subapp_filter(nil, [:action_name]) + controller.impressionist_subapp_filter(nil, [:action_name]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different controller" do + controller.stub!(:controller_name).and_return("post") + controller.impressionist_subapp_filter(nil, [:controller_name]) + controller.impressionist_subapp_filter(nil, [:controller_name]) + Impression.should have(@impression_count + 1).records + controller.stub!(:controller_name).and_return("article") + controller.impressionist_subapp_filter(nil, [:controller_name]) + controller.impressionist_subapp_filter(nil, [:controller_name]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different session" do + controller.stub!(:session_hash).and_return("foo") + controller.impressionist_subapp_filter(nil, [:session_hash]) + controller.impressionist_subapp_filter(nil, [:session_hash]) + Impression.should have(@impression_count + 1).records + controller.stub!(:session_hash).and_return("bar") + controller.impressionist_subapp_filter(nil, [:session_hash]) + controller.impressionist_subapp_filter(nil, [:session_hash]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different ip" do + controller.request.stub!(:remote_ip).and_return("1.2.3.4") + controller.impressionist_subapp_filter(nil, [:ip_address]) + controller.impressionist_subapp_filter(nil, [:ip_address]) + Impression.should have(@impression_count + 1).records + controller.request.stub!(:remote_ip).and_return("5.6.7.8") + controller.impressionist_subapp_filter(nil, [:ip_address]) + controller.impressionist_subapp_filter(nil, [:ip_address]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different referer" do + controller.request.stub!(:referer).and_return("http://foo/bar") + controller.impressionist_subapp_filter(nil, [:referrer]) + controller.impressionist_subapp_filter(nil, [:referrer]) + Impression.should have(@impression_count + 1).records + controller.request.stub!(:referer).and_return("http://bar/fo") + controller.impressionist_subapp_filter(nil, [:referrer]) + controller.impressionist_subapp_filter(nil, [:referrer]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different id" do + controller.stub!(:params).and_return({:id => "666"}) # for correct impressionable id in filter + controller.impressionist_subapp_filter(nil, [:impressionable_type, :impressionable_id]) + controller.impressionist_subapp_filter(nil, [:impressionable_type, :impressionable_id]) + controller.stub!(:params).and_return({:id => "42"}) # for correct impressionable id in filter + controller.impressionist_subapp_filter(nil, [:impressionable_type, :impressionable_id]) + controller.impressionist_subapp_filter(nil, [:impressionable_type, :impressionable_id]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize combined uniqueness" do + controller.stub!(:action_name).and_return("test_action") + controller.impressionist_subapp_filter(nil, [:ip_address, :request_hash, :action_name]) + controller.impressionist_subapp_filter(nil, [:request_hash, :ip_address, :action_name]) + controller.impressionist_subapp_filter(nil, [:request_hash, :action_name]) + controller.impressionist_subapp_filter(nil, [:ip_address, :action_name]) + controller.impressionist_subapp_filter(nil, [:ip_address, :request_hash]) + controller.impressionist_subapp_filter(nil, [:action_name]) + controller.impressionist_subapp_filter(nil, [:ip_address]) + controller.impressionist_subapp_filter(nil, [:request_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize combined non-uniqueness" do + controller.stub!(:action_name).and_return(nil) + controller.impressionist_subapp_filter(nil, [:ip_address, :action_name]) + controller.stub!(:action_name).and_return("test_action") + controller.impressionist_subapp_filter(nil, [:ip_address, :action_name]) + controller.stub!(:action_name).and_return("another_action") + controller.impressionist_subapp_filter(nil, [:ip_address, :action_name]) + Impression.should have(@impression_count + 3).records + end + + end + + describe "impressionist method uniqueness for impressionables" do + + # in this test we reuse the post model. might break if model changes. + + it "should ignore uniqueness if not requested" do + impressionable = Post.create + controller.impressionist impressionable + controller.impressionist impressionable + Impression.should have(@impression_count + 2).records + end + + it "should recognize unique session" do + controller.stub!(:session_hash).and_return(request.session_options[:id]) + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique ip" do + controller.request.stub!(:remote_ip).and_return("1.2.3.4") + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique request" do + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:request_hash]) + controller.impressionist(impressionable, nil, :unique => [:request_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique user" do + controller.stub!(:user_id).and_return(666) + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:user_id]) + controller.impressionist(impressionable, nil, :unique => [:user_id]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize unique referer" do + controller.request.stub!(:referer).and_return("http://foo/bar") + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:referrer]) + controller.impressionist(impressionable, nil, :unique => [:referrer]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize different session" do + impressionable = Post.create + controller.stub!(:session_hash).and_return("foo") + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + Impression.should have(@impression_count + 1).records + controller.stub!(:session_hash).and_return("bar") + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different ip" do + controller.request.stub!(:remote_ip).and_return("1.2.3.4") + impressionable = Post.create + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + Impression.should have(@impression_count + 1).records + controller.request.stub!(:remote_ip).and_return("5.6.7.8") + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize different user" do + impressionable = Post.create + controller.stub!(:user_id).and_return(666) + controller.impressionist(impressionable, nil, :unique => [:user_id]) + controller.impressionist(impressionable, nil, :unique => [:user_id]) + Impression.should have(@impression_count + 1).records + controller.stub!(:user_id).and_return(42) + controller.impressionist(impressionable, nil, :unique => [:user_id]) + controller.impressionist(impressionable, nil, :unique => [:user_id]) + Impression.should have(@impression_count + 2).records + end + + it "should recognize combined uniqueness" do + impressionable = Post.create + controller.stub!(:session_hash).and_return("foo") + controller.impressionist(impressionable, nil, :unique => [:ip_address, :request_hash, :session_hash]) + controller.impressionist(impressionable, nil, :unique => [:request_hash, :ip_address, :session_hash]) + controller.impressionist(impressionable, nil, :unique => [:request_hash, :session_hash]) + controller.impressionist(impressionable, nil, :unique => [:ip_address, :session_hash]) + controller.impressionist(impressionable, nil, :unique => [:ip_address, :request_hash]) + controller.impressionist(impressionable, nil, :unique => [:session_hash]) + controller.impressionist(impressionable, nil, :unique => [:ip_address]) + controller.impressionist(impressionable, nil, :unique => [:request_hash]) + Impression.should have(@impression_count + 1).records + end + + it "should recognize combined non-uniqueness" do + impressionable = Post.create + controller.stub!(:session_hash).and_return(nil) + controller.impressionist(impressionable, nil, :unique => [:ip_address, :session_hash]) + controller.stub!(:session_hash).and_return("foo") + controller.impressionist(impressionable, nil, :unique => [:ip_address, :session_hash]) + controller.stub!(:session_hash).and_return("bar") + controller.impressionist(impressionable, nil, :unique => [:ip_address, :session_hash]) + Impression.should have(@impression_count + 3).records + end + + end + + describe "impressionist filter and method uniqueness" do + + it "should recognize uniqueness" do + impressionable = Post.create + controller.stub!(:controller_name).and_return("posts") # for correct impressionable type in filter + controller.stub!(:params).and_return({:id => impressionable.id.to_s}) # for correct impressionable id in filter + controller.stub!(:session_hash).and_return("foo") + controller.request.stub!(:remote_ip).and_return("1.2.3.4") + # order of the following methods is important for the test! + controller.impressionist_subapp_filter(nil, [:ip_address, :request_hash, :session_hash]) + controller.impressionist(impressionable, nil, :unique => [:ip_address, :request_hash, :session_hash]) + Impression.should have(@impression_count + 1).records + end + + end + +end + diff --git a/vendor/impressionist/test_app/spec/fixtures/articles.yml b/vendor/impressionist/test_app/spec/fixtures/articles.yml new file mode 100644 index 00000000..0a0867df --- /dev/null +++ b/vendor/impressionist/test_app/spec/fixtures/articles.yml @@ -0,0 +1,3 @@ +one: + id: 1 + name: Test Article diff --git a/vendor/impressionist/test_app/spec/fixtures/impressions.yml b/vendor/impressionist/test_app/spec/fixtures/impressions.yml new file mode 100644 index 00000000..7342af54 --- /dev/null +++ b/vendor/impressionist/test_app/spec/fixtures/impressions.yml @@ -0,0 +1,43 @@ +<% 1.upto(7) do |i| %> +impression<%= i %>: + impressionable_type: Article + impressionable_id: 1 + request_hash: a<%=i%> + session_hash: b<%=i%> + ip_address: 127.0.0.<%=i%> + created_at: 2011-01-01 +<% end %> + + +impression8: + impressionable_type: Article + impressionable_id: 1 + request_hash: a1 + session_hash: b1 + ip_address: 127.0.0.1 + created_at: 2010-01-01 + +impression9: + impressionable_type: Article + impressionable_id: 1 + request_hash: a1 + session_hash: b2 + ip_address: 127.0.0.1 + created_at: 2011-01-03 + +impression10: + impressionable_type: Article + impressionable_id: 1 + request_hash: a9 + session_hash: b3 + ip_address: 127.0.0.8 + created_at: 2010-01-01 + +impression11: + impressionable_type: Article + impressionable_id: 1 + request_hash: a10 + session_hash: b4 + ip_address: 127.0.0.1 + created_at: 2010-01-01 + diff --git a/vendor/impressionist/test_app/spec/fixtures/posts.yml b/vendor/impressionist/test_app/spec/fixtures/posts.yml new file mode 100644 index 00000000..b85e6799 --- /dev/null +++ b/vendor/impressionist/test_app/spec/fixtures/posts.yml @@ -0,0 +1,3 @@ +one: + id: 1 + name: Test Post diff --git a/vendor/impressionist/test_app/spec/fixtures/widgets.yml b/vendor/impressionist/test_app/spec/fixtures/widgets.yml new file mode 100644 index 00000000..11962466 --- /dev/null +++ b/vendor/impressionist/test_app/spec/fixtures/widgets.yml @@ -0,0 +1,4 @@ +one: + id: 1 + name: A Widget + impressions_count: 0 diff --git a/vendor/impressionist/test_app/spec/initializers/initializers_spec.rb b/vendor/impressionist/test_app/spec/initializers/initializers_spec.rb new file mode 100644 index 00000000..879a82a4 --- /dev/null +++ b/vendor/impressionist/test_app/spec/initializers/initializers_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Impressionist do + it "should be extended from ActiveRecord::Base" do + method = RUBY_VERSION.match("1.8") ? "is_impressionable" : :is_impressionable + ActiveRecord::Base.methods.include?(method).should be_true + end + + it "should include methods in ApplicationController" do + method = RUBY_VERSION.match("1.8") ? "impressionist" : :impressionist + ApplicationController.instance_methods.include?(method).should be_true + end + + it "should include the before_filter method in ApplicationController" do + filters = ApplicationController._process_action_callbacks.select { |c| c.kind == :before } + filters.collect{|filter|filter.filter}.include?(:impressionist_app_filter).should be_true + end +end diff --git a/vendor/impressionist/test_app/spec/models/bots_spec.rb b/vendor/impressionist/test_app/spec/models/bots_spec.rb new file mode 100644 index 00000000..a2983587 --- /dev/null +++ b/vendor/impressionist/test_app/spec/models/bots_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Impressionist::Bots do + + describe "self.bot?" do + it "is true if user_agent is matches wild card" do + Impressionist::Bots.bot?("google.com bot").should be_true + end + + it "is true if user_agent is on bot list" do + Impressionist::Bots.bot?("A-Online Search").should be_true + end + + it "is false if user_agent is blank" do + Impressionist::Bots.bot?("").should be_false + Impressionist::Bots.bot?(nil).should be_false + end + + it "is false if user_agent is safe" do + Impressionist::Bots.bot?('127.0.0.1').should be_false + end + + it "is false if user_agent not given" do + Impressionist::Bots.bot?.should be_false + end + end +end \ No newline at end of file diff --git a/vendor/impressionist/test_app/spec/models/counter_caching_spec.rb b/vendor/impressionist/test_app/spec/models/counter_caching_spec.rb new file mode 100644 index 00000000..552bf647 --- /dev/null +++ b/vendor/impressionist/test_app/spec/models/counter_caching_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe Impression do + fixtures :widgets + + before(:each) do + @widget = Widget.find(1) + Impression.destroy_all + end + + describe "self#impressionist_counter_caching?" do + it "should know when counter caching is enabled" do + Widget.should be_impressionist_counter_caching + end + + it "should know when counter caching is disabled" do + Article.should_not be_impressionist_counter_caching + end + end + + describe "self#counter_caching?" do + it "should know when counter caching is enabled" do + ActiveSupport::Deprecation.should_receive(:warn) + Widget.should be_counter_caching + end + + it "should know when counter caching is disabled" do + ActiveSupport::Deprecation.should_receive(:warn) + Article.should_not be_counter_caching + end + + end + + describe "#update_impressionist_counter_cache" do + it "should update the counter cache column to reflect the correct number of impressions" do + lambda { + @widget.impressions.create(:request_hash => 'abcd1234') + @widget.reload + }.should change(@widget, :impressions_count).from(0).to(1) + end + end + +end diff --git a/vendor/impressionist/test_app/spec/models/model_spec.rb b/vendor/impressionist/test_app/spec/models/model_spec.rb new file mode 100644 index 00000000..54601662 --- /dev/null +++ b/vendor/impressionist/test_app/spec/models/model_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe Impression do + fixtures :articles,:impressions,:posts + + before(:each) do + @article = Article.find(1) + end + + it "should save a blank impression for an Article that has 10 impressions" do + @article.impressions.create + @article.impressions.size.should eq 12 + end + + it "should save an impression with a message" do + @article.impressions.create(:message=>"test message") + @article.impressions.last.message.should eq "test message" + end + + it "should return the impression count for all with no date range specified" do + @article.impressionist_count(:filter=>:all).should eq 11 + end + + it "should return unique impression count with no date range specified" do + @article.impressionist_count.should eq 9 + end + + it "should return impression count with only start date specified" do + @article.impressionist_count(:start_date=>"2011-01-01",:filter=>:all).should eq 8 + end + + it "should return impression count with whole date range specified" do + @article.impressionist_count(:start_date=>"2011-01-01",:end_date=>"2011-01-02",:filter=>:all).should eq 7 + end + + it "should return unique impression count with only start date specified" do + @article.impressionist_count(:start_date=>"2011-01-01").should eq 7 + end + + it "should return unique impression count with date range specified" do + @article.impressionist_count(:start_date=>"2011-01-01",:end_date=>"2011-01-02").should eq 7 + end + + it "should return unique impression count using ip address (which in turn eliminates duplicate request_hashes)" do + @article.impressionist_count(:filter=>:ip_address).should eq 8 + end + + it "should return unique impression count using session_hash (which in turn eliminates duplicate request_hashes)" do + @article.impressionist_count(:filter=>:session_hash).should eq 7 + end + + # tests :dependent => :destroy + it "should delete impressions on deletion of impressionable" do + impressions_count = Impression.all.size + a = Article.create + i = a.impressions.create + a.destroy + a.destroyed?.should be_true + i.destroyed?.should be_true + end + + #OLD COUNT METHODS. DEPRECATE SOON + it "should return the impression count with no date range specified" do + @article.impression_count.should eq 11 + end + + it "should return unique impression count with no date range specified" do + @article.unique_impression_count.should eq 9 + end + + it "should return impression count with only start date specified" do + @article.impression_count("2011-01-01").should eq 8 + end + + it "should return impression count with whole date range specified" do + @article.impression_count("2011-01-01","2011-01-02").should eq 7 + end + + it "should return unique impression count with only start date specified" do + @article.unique_impression_count("2011-01-01").should eq 7 + end + + it "should return unique impression count with date range specified" do + @article.unique_impression_count("2011-01-01","2011-01-02").should eq 7 + end + + it "should return unique impression count using ip address (which in turn eliminates duplicate request_hashes)" do + @article.unique_impression_count_ip.should eq 8 + end + + it "should return unique impression count using session_hash (which in turn eliminates duplicate request_hashes)" do + @article.unique_impression_count_session.should eq 7 + end +end diff --git a/vendor/impressionist/test_app/spec/rails_generators/rails_generators_spec.rb b/vendor/impressionist/test_app/spec/rails_generators/rails_generators_spec.rb new file mode 100644 index 00000000..2b5ea938 --- /dev/null +++ b/vendor/impressionist/test_app/spec/rails_generators/rails_generators_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' +require 'systemu' + +# FIXME this test might break the others if run before them +# +describe Impressionist do + fixtures :articles,:impressions,:posts + it "should delete existing migration and generate the migration file" do + pending + migrations_dir = "#{Rails.root}/db/migrate" + impressions_migration = Dir.entries(migrations_dir).grep(/impressions/)[0] + File.delete("#{migrations_dir}/#{impressions_migration}") unless impressions_migration.blank? + generator_output = systemu("rails g impressionist")[1] + migration_name = generator_output.split("migrate/")[1].strip + Dir.entries(migrations_dir).include?(migration_name).should be_true + end + + it "should run the migration created in the previous spec" do + pending + migrate_output = systemu("rake db:migrate RAILS_ENV=test") + migrate_output[1].include?("CreateImpressionsTable: migrated").should be_true + end +end diff --git a/vendor/impressionist/test_app/spec/spec_helper.rb b/vendor/impressionist/test_app/spec/spec_helper.rb new file mode 100644 index 00000000..9cbe63b7 --- /dev/null +++ b/vendor/impressionist/test_app/spec/spec_helper.rb @@ -0,0 +1,36 @@ +ENV["RAILS_ENV"] ||= 'test' +unless ENV['CI'] + require 'simplecov' + SimpleCov.start 'rails' +end +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' + +# Requires supporting ruby files with custom matchers and macros, etc, +# in spec/support/ and its subdirectories. +Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} + +RSpec.configure do |config| + # == Mock Framework + # + # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: + # + # config.mock_with :mocha + # config.mock_with :flexmock + # config.mock_with :rr + config.mock_with :rspec + + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # make the rails logger usable in the tests as logger.xxx "..." + def logger + Rails.logger + end + +end diff --git a/vendor/impressionist/upgrade_migrations/version_0_3_0.rb b/vendor/impressionist/upgrade_migrations/version_0_3_0.rb new file mode 100644 index 00000000..46681250 --- /dev/null +++ b/vendor/impressionist/upgrade_migrations/version_0_3_0.rb @@ -0,0 +1,27 @@ +class CreateImpressionsTable < ActiveRecord::Migration + def self.up + add_column :impressions, :session_hash, :string + remove_index :impressions, :name => :poly_index + remove_index :impressions, :name => :controlleraction_index + add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false + add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false + add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false + + end + + def self.down + remove_column :impressions, :session_hash + remove_index :impressions, :name => :poly_request_index + remove_index :impressions, :name => :poly_ip_index + remove_index :impressions, :name => :poly_session_index + remove_index :impressions, :name => :controlleraction_request_index + remove_index :impressions, :name => :controlleraction_ip_index + remove_index :impressions, :name => :controlleraction_session_index + remove_index :impressions, :user_id + add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash, :ip_address], :name => "poly_index", :unique => false + add_index :impressions, [:controller_name,:action_name,:request_hash,:ip_address], :name => "controlleraction_index", :unique => false + end +end diff --git a/vendor/impressionist/upgrade_migrations/version_0_4_0.rb b/vendor/impressionist/upgrade_migrations/version_0_4_0.rb new file mode 100644 index 00000000..ebd7fac5 --- /dev/null +++ b/vendor/impressionist/upgrade_migrations/version_0_4_0.rb @@ -0,0 +1,9 @@ +class Version04UpdateImpressionsTable < ActiveRecord::Migration + def self.up + add_column :impressions, :referrer, :string + end + + def self.down + remove_column :impressions, :referrer + end +end diff --git a/vendor/impressionist/upgrade_migrations/version_1_1_2.rb b/vendor/impressionist/upgrade_migrations/version_1_1_2.rb new file mode 100644 index 00000000..a4ef8e9a --- /dev/null +++ b/vendor/impressionist/upgrade_migrations/version_1_1_2.rb @@ -0,0 +1,9 @@ +class Version04UpdateImpressionsTable < ActiveRecord::Migration + def self.up + add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false + end + + def self.down + remove_index :impressions, :impressionable_type_message_index + end +end From 5e9b55e08e8c14bdc5868f13fd48f08e5e87b058 Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 22:47:28 +0800 Subject: [PATCH 09/11] Ray took out 'default' tags --- .../panel/announcement/back_end/bulletins/_form.html.erb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb b/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb index 94964543..4e3368b2 100644 --- a/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb +++ b/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb @@ -99,12 +99,6 @@ <% end %> <% end %>

    - Default - Default - Default - Default -
    -
    <% if params[:action] != 'new' %> From 80e84c10ba4738b9bdeedb92c5f95b314400221f Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Tue, 15 May 2012 23:01:00 +0800 Subject: [PATCH 10/11] Remove link from dashboard --- app/views/admin/dashboards/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/dashboards/index.html.erb b/app/views/admin/dashboards/index.html.erb index e81287ac..dbca5aa6 100644 --- a/app/views/admin/dashboards/index.html.erb +++ b/app/views/admin/dashboards/index.html.erb @@ -527,7 +527,7 @@
    -

    <%= t(:most_visited_page) %>

    +

    <%= t(:most_visited_page) %>

    From 0b0916179fb5b6154eb19610ebdc426663bcb8c5 Mon Sep 17 00:00:00 2001 From: Christophe Vilayphiou Date: Wed, 16 May 2012 11:07:11 +0800 Subject: [PATCH 11/11] Change assets --- app/assets/javascripts/new_admin.js | 1 + app/assets/javascripts/tinymce_orbit.js | 97 +++++++++++++++---- .../admin/asset_categories_controller.rb | 65 +++++++++++++ app/controllers/admin/assets_controller.rb | 19 ++-- app/models/asset.rb | 3 + app/models/asset_category.rb | 16 +++ app/uploaders/asset_uploader.rb | 6 +- .../asset_categories/_asset_category.html.erb | 17 ++++ .../admin/asset_categories/_form.html.erb | 30 ++++++ .../admin/asset_categories/create.js.erb | 2 + .../admin/asset_categories/destroy.js.erb | 1 + app/views/admin/asset_categories/edit.js.erb | 1 + .../admin/asset_categories/index.html.erb | 41 ++++++++ app/views/admin/asset_categories/new.js.erb | 1 + .../admin/asset_categories/update.js.erb | 4 + app/views/admin/assets/_asset.html.erb | 21 ++-- app/views/admin/assets/_edit.html.erb | 2 +- app/views/admin/assets/_filter.html.erb | 11 +++ app/views/admin/assets/_form.html.erb | 5 + app/views/admin/assets/_sort_headers.html.erb | 5 + app/views/admin/assets/index.html.erb | 55 ++++++----- config/environments/development.rb | 3 + config/routes.rb | 7 +- .../back_end/bulletins/_form.html.erb | 4 +- 24 files changed, 346 insertions(+), 71 deletions(-) create mode 100644 app/controllers/admin/asset_categories_controller.rb create mode 100644 app/models/asset_category.rb create mode 100644 app/views/admin/asset_categories/_asset_category.html.erb create mode 100644 app/views/admin/asset_categories/_form.html.erb create mode 100644 app/views/admin/asset_categories/create.js.erb create mode 100644 app/views/admin/asset_categories/destroy.js.erb create mode 100644 app/views/admin/asset_categories/edit.js.erb create mode 100644 app/views/admin/asset_categories/index.html.erb create mode 100644 app/views/admin/asset_categories/new.js.erb create mode 100644 app/views/admin/asset_categories/update.js.erb create mode 100644 app/views/admin/assets/_filter.html.erb create mode 100644 app/views/admin/assets/_sort_headers.html.erb diff --git a/app/assets/javascripts/new_admin.js b/app/assets/javascripts/new_admin.js index f20267cd..70bc22bd 100644 --- a/app/assets/javascripts/new_admin.js +++ b/app/assets/javascripts/new_admin.js @@ -15,4 +15,5 @@ //= require tinymce_orbit //= require orbit-bar-search //= require side_bar_history +//= require rss //= require ajax_form \ No newline at end of file diff --git a/app/assets/javascripts/tinymce_orbit.js b/app/assets/javascripts/tinymce_orbit.js index 71264fde..9e0853e3 100644 --- a/app/assets/javascripts/tinymce_orbit.js +++ b/app/assets/javascripts/tinymce_orbit.js @@ -1,29 +1,90 @@ function load_tinymce() { $('.tinymce_textarea').tinymce({ + + // General options theme: 'advanced', - plugins : "autolink,lists,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template", + file_browser_callback : 'myFileBrowser', + plugins : "autolink,lists,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template", - // Theme options - theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,formatselect,fontselect,fontsizeselect", - theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,bullist,numlist,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor", - theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,fullscreen", - theme_advanced_toolbar_location : "top", - theme_advanced_toolbar_align : "left", - theme_advanced_statusbar_location : "bottom", - theme_advanced_resizing : true, + // Theme options + theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,formatselect,fontselect,fontsizeselect", + theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,bullist,numlist,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor", + theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,fullscreen", + theme_advanced_toolbar_location : "top", + theme_advanced_toolbar_align : "left", + theme_advanced_statusbar_location : "bottom", + theme_advanced_resizing : true, - // Skin options - skin : "o2k7", - skin_variant : "silver", + // Skin options + skin : "o2k7", + skin_variant : "silver", - // Drop lists for link/image/media/template dialogs - template_external_list_url : "js/template_list.js", - external_link_list_url : "js/link_list.js", - external_image_list_url : "js/image_list.js", - media_external_list_url : "js/media_list.js" + // Drop lists for link/image/media/template dialogs + template_external_list_url : "js/template_list.js", + // external_link_list_url : "js/link_list.js", + // external_image_list_url : "js/image_list.js", + // media_external_list_url : "js/media_list.js" + + // Style formats + style_formats : [ + {title : 'Bold text', inline : 'b'}, + {title : 'Red text', inline : 'span', styles : {color : '#ff0000'}}, + {title : 'Red header', block : 'h1', styles : {color : '#ff0000'}}, + {title : 'Example 1', inline : 'span', classes : 'example1'}, + {title : 'Example 2', inline : 'span', classes : 'example2'}, + {title : 'Table styles'}, + {title : 'Table row 1', selector : 'tr', classes : 'tablerow1'} + ], + + // Replace values for the template plugin + template_replace_values : { + username : "Some User", + staffid : "991234" + } }); + function myFileBrowser (field_name, url, type, win) { + + + var cmsURL = window.location.toString(); + cmsURL = cmsURL.split("/"); + // cmsURL = "http:///modules/modules/filemanager/"; + + // script URL - use an absolute path! + if (cmsURL.indexOf("?") < 0) { + //add the type as the only query parameter + cmsURL = cmsURL + "?type=" + type; + } + else { + //add the type as an additional query parameter + // (PHP session ID is now included if there is one at all) + cmsURL = cmsURL + "&type=" + type; + } + + tinyMCE.activeEditor.windowManager.open({ + file : cmsURL, + title : 'File Browser', + width : 850, // Your dimensions may differ - toy around with them! + height : 455, + resizable : "no", + inline : "no", // This parameter only has an effect if you use the inlinepopups plugin! + close_previous : "no" + }, { + window : win, + input : field_name + }); + return false; + } + function ajaxSave() { + var ed = tinyMCE.get('content'); + // Do you ajax call here, window.setTimeout fakes ajax call + ed.setProgressState(1); // Show progress + window.setTimeout(function() { + ed.setProgressState(0); // Hide progress + alert(ed.getContent()); + }, 3000); + } } $(document).ready(function() { load_tinymce(); -}); \ No newline at end of file +}); diff --git a/app/controllers/admin/asset_categories_controller.rb b/app/controllers/admin/asset_categories_controller.rb new file mode 100644 index 00000000..22efd03f --- /dev/null +++ b/app/controllers/admin/asset_categories_controller.rb @@ -0,0 +1,65 @@ +class Admin::AssetCategoriesController < OrbitBackendController + + def index + @asset_categories = AssetCategory.all + @asset_category = AssetCategory.new + @url = admin_asset_categories_path + + end + + def show + @asset_category = AssetCategory.find(params[:id]) + end + + def new + @asset_category = AssetCategory.new + end + + def edit + @asset_category = AssetCategory.find(params[:id]) + @i18n_variable = @asset_category.i18n_variable + @url = admin_asset_categories_path(@asset_category) + end + + def create + @asset_category = AssetCategory.new(params[:asset_category]) + + respond_to do |format| + if @asset_category.save + format.html { redirect_to(admin_asset_categories_url, :notice => t('announcement.create_asset_category_success')) } + format.js + else + format.html { render :action => "new" } + format.js { render action: "new" } + end + end + end + + def update + @asset_category = AssetCategory.find(params[:id]) + + @url = admin_asset_categories_path(@asset_category) + + respond_to do |format| + if @asset_category.update_attributes(params[:asset_category]) + # format.html { redirect_to(panel_announcement_back_end_asset_category_url(@asset_category), :notice => t('asset_category.update_asset_category_success')) } + # format.html { redirect_to(panel_announcement_back_end_asset_categories_url, :notice => t('asset_category.update_asset_category_success')) } + # format.xml { head :ok } + format.js + else + format.html { render :action => "edit" } + format.js { render :action => "edit" } + end + end + end + + def destroy + @asset_category = AssetCategory.find(params[:id]) + @asset_category.destroy + + respond_to do |format| + format.html { redirect_to(admin_asset_categories_url) } + format.js + end + end +end diff --git a/app/controllers/admin/assets_controller.rb b/app/controllers/admin/assets_controller.rb index 1591cf39..b595ed5b 100644 --- a/app/controllers/admin/assets_controller.rb +++ b/app/controllers/admin/assets_controller.rb @@ -1,11 +1,7 @@ -class Admin::AssetsController < ApplicationController - - layout "admin" - before_filter :authenticate_user! - before_filter :is_admin? +class Admin::AssetsController < OrbitBackendController def index - @assets = Asset.all.entries + @assets = (params[:sort] || @filter) ? get_sorted_and_filtered("asset") : Asset.all.page(params[:page]).per(10) end def show @@ -14,6 +10,7 @@ class Admin::AssetsController < ApplicationController def new @asset = Asset.new + @asset_categories = AssetCategory.all respond_to do |format| format.html {} format.js { render 'js/show_pop_up', :locals => {:partial => 'admin/assets/new'} } @@ -22,6 +19,7 @@ class Admin::AssetsController < ApplicationController def edit @asset = Asset.find(params[:id]) + @asset_categories = AssetCategory.all respond_to do |format| format.html {} format.js { render 'js/show_pop_up', :locals => {:partial => 'admin/assets/edit'} } @@ -33,7 +31,7 @@ class Admin::AssetsController < ApplicationController if @asset.save respond_to do |format| format.html { redirect_to admin_assets_url } - format.js { render 'js/remove_pop_up_and_reload_content', :locals => {:function => 'append', :id => 'asset_tbody', :value => @asset, :values => nil, :partial => 'admin/assets/asset', :locals => nil} } + format.js { render 'js/remove_pop_up_and_reload_content', :locals => {:function => 'append', :id => 'tbody_assets', :value => @asset, :values => nil, :partial => 'admin/assets/asset', :locals => nil} } end else respond_to do |format| @@ -66,5 +64,12 @@ class Admin::AssetsController < ApplicationController format.js { render 'js/remove_element', :locals => {:id => "asset_#{@asset.id}"} } end end + + def delete + if params[:to_delete] + asset = Asset.any_in(:_id => params[:to_delete]).delete_all + end + redirect_to assets_url(:filter => params[:filter], :direction => params[:direction], :sort => params[:sort], :sort_options => params[:sort_options]) + end end diff --git a/app/models/asset.rb b/app/models/asset.rb index c44e8be1..2bb02dfc 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -9,5 +9,8 @@ class Asset field :description validates_presence_of :filename, :data + + belongs_to :asset_category + belongs_to :assetable, polymorphic: true end diff --git a/app/models/asset_category.rb b/app/models/asset_category.rb new file mode 100644 index 00000000..89bceaf7 --- /dev/null +++ b/app/models/asset_category.rb @@ -0,0 +1,16 @@ +class AssetCategory + include Mongoid::Document + include Mongoid::Timestamps + + field :key + field :display + + has_one :i18n_variable, :as => :language_value, :autosave => true, :dependent => :destroy + + has_many :assets + + def self.from_id(id) + AssetCategory.find(id) rescue nil + end + +end \ No newline at end of file diff --git a/app/uploaders/asset_uploader.rb b/app/uploaders/asset_uploader.rb index 1e07bca6..f59e20a0 100644 --- a/app/uploaders/asset_uploader.rb +++ b/app/uploaders/asset_uploader.rb @@ -48,8 +48,8 @@ class AssetUploader < CarrierWave::Uploader::Base # end # Override the filename of the uploaded files: - # def filename - # "something.jpg" if original_filename - # end + def filename + model.filename + end end diff --git a/app/views/admin/asset_categories/_asset_category.html.erb b/app/views/admin/asset_categories/_asset_category.html.erb new file mode 100644 index 00000000..d7047e36 --- /dev/null +++ b/app/views/admin/asset_categories/_asset_category.html.erb @@ -0,0 +1,17 @@ + + + + <% @site_valid_locales.each do |locale| %> + + <% end %> + diff --git a/app/views/admin/asset_categories/_form.html.erb b/app/views/admin/asset_categories/_form.html.erb new file mode 100644 index 00000000..5c64146f --- /dev/null +++ b/app/views/admin/asset_categories/_form.html.erb @@ -0,0 +1,30 @@ +<% # encoding: utf-8 %> + +<%= form_for(@asset_category, :remote => true, :url => @url) do |f| %> + +

    <%= (@asset_category.new_record? ? 'Add' : 'Edit') %>

    + +
    + <%= f.label :key %> + <%= f.text_field :key %> +
    + +
    + <%= f.fields_for :i18n_variable, (@asset_category.new_record? ? @asset_category.build_i18n_variable : @asset_category.i18n_variable) do |f| %> + <% @site_valid_locales.each do |locale| %> +
    + <%= label_tag "name-#{locale}", "Name-#{I18nVariable.from_locale(locale)}", :class => 'control-label' %> +
    + <%= f.text_field locale, :class => 'input-xxlarge' %> +
    +
    + <% end %> + <% end %> +
    + +
    + <%= f.submit t('submit'), :class=>'btn btn-primary' %> +
    + +<% end %> + \ No newline at end of file diff --git a/app/views/admin/asset_categories/create.js.erb b/app/views/admin/asset_categories/create.js.erb new file mode 100644 index 00000000..8da391d7 --- /dev/null +++ b/app/views/admin/asset_categories/create.js.erb @@ -0,0 +1,2 @@ +$('<%= j render :partial => 'asset_category', :collection => [@asset_category] %>').appendTo('#asset_categories').hide().fadeIn(); +$("#new_asset_category")[0].reset(); \ No newline at end of file diff --git a/app/views/admin/asset_categories/destroy.js.erb b/app/views/admin/asset_categories/destroy.js.erb new file mode 100644 index 00000000..4c336e78 --- /dev/null +++ b/app/views/admin/asset_categories/destroy.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id @asset_category %>").remove(); \ No newline at end of file diff --git a/app/views/admin/asset_categories/edit.js.erb b/app/views/admin/asset_categories/edit.js.erb new file mode 100644 index 00000000..eaff01fa --- /dev/null +++ b/app/views/admin/asset_categories/edit.js.erb @@ -0,0 +1 @@ +$("#form > form").replaceWith("<%= j render "form" %>"); \ No newline at end of file diff --git a/app/views/admin/asset_categories/index.html.erb b/app/views/admin/asset_categories/index.html.erb new file mode 100644 index 00000000..f1d1a787 --- /dev/null +++ b/app/views/admin/asset_categories/index.html.erb @@ -0,0 +1,41 @@ + +<%= flash_messages %> + +
    + <%= asset_category.key %> +
    + +
    +
    <%= asset_category.i18n_variable[locale] rescue nil %>
    + + + + <% @site_valid_locales.each do |locale| %> + + <% end %> + + +
    <%= t('asset_category.key') %><%= I18nVariable.first(:conditions => {:key => locale})[I18n.locale] %>
    +
    +
    + + + + + + + + <% @site_valid_locales.each do |locale| %> + + <% end %> + + + + + <%= render :partial => 'asset_category', :collection => @asset_categories %> + + +
    + +
    <%= render :partial => "form" %>
    + + + diff --git a/app/views/admin/asset_categories/new.js.erb b/app/views/admin/asset_categories/new.js.erb new file mode 100644 index 00000000..40061b9f --- /dev/null +++ b/app/views/admin/asset_categories/new.js.erb @@ -0,0 +1 @@ +$("#form > form").replaceWith("<%= j render "form" %>"); diff --git a/app/views/admin/asset_categories/update.js.erb b/app/views/admin/asset_categories/update.js.erb new file mode 100644 index 00000000..75ef202e --- /dev/null +++ b/app/views/admin/asset_categories/update.js.erb @@ -0,0 +1,4 @@ +$("#<%= dom_id @asset_category %>").replaceWith("<%= j render :partial => 'asset_category', :collection => [@asset_category] %>"); +<% @asset_category = AssetCategory.new(:display => 'List') # reset for new form %> +$(".edit_asset_category").replaceWith("<%= j render "form" %>") +$(".new_asset_category")[0].reset(); \ No newline at end of file diff --git a/app/views/admin/assets/_asset.html.erb b/app/views/admin/assets/_asset.html.erb index ea71dc6c..99ac636e 100644 --- a/app/views/admin/assets/_asset.html.erb +++ b/app/views/admin/assets/_asset.html.erb @@ -1,17 +1,16 @@ - - <%= asset.id %> -
    <%= image_tag(asset.data.url) %>
    - <%#= link_to asset.filename, asset.data.url, :target => '_blank' %> + + <%= check_box_tag 'to_delete[]', asset.id, false, :class => "checkbox_in_list" %> + +
    <%= image_tag(asset.data.url) %>
    +
    + +
    <%= asset.description %> <%= asset.data.file.content_type %> <%= asset.data_identifier %> <%= number_to_human_size(asset.data.file.file_length) %> - - <%= link_to t(:edit), edit_admin_asset_path(asset), :remote => true, :class => 'edit' %> - <%= link_to t(:delete), admin_asset_path(asset), :confirm => t('sure?'), :method => :delete, :remote => true, :class => 'delete' %> - - - - \ No newline at end of file diff --git a/app/views/admin/assets/_edit.html.erb b/app/views/admin/assets/_edit.html.erb index a747d4f2..a62524c0 100644 --- a/app/views/admin/assets/_edit.html.erb +++ b/app/views/admin/assets/_edit.html.erb @@ -9,7 +9,7 @@ <%= link_back %> <%= f.submit t(:edit) %> <% else %> - <%= t(:edit) %> + <%= t(:update) %> <% end %> <% end %> diff --git a/app/views/admin/assets/_filter.html.erb b/app/views/admin/assets/_filter.html.erb new file mode 100644 index 00000000..02ba839b --- /dev/null +++ b/app/views/admin/assets/_filter.html.erb @@ -0,0 +1,11 @@ + + +<% content_for :page_specific_javascript do %> + <%= javascript_include_tag "sort_header" %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/assets/_form.html.erb b/app/views/admin/assets/_form.html.erb index 72199ceb..6abb883e 100644 --- a/app/views/admin/assets/_form.html.erb +++ b/app/views/admin/assets/_form.html.erb @@ -8,6 +8,11 @@ <%= f.text_field :description, :class => 'text' %>

    +

    +<%= f.label :category %> +<%= f.select :asset_category_id, @asset_categories.collect{|t| [ t.i18n_variable[I18n.locale], t.id ]}, {}, :class => "input-medium" %> +

    +

    <%= f.label :data, t('admin.data'), :class => 'file' %> <%= f.file_field :data %> diff --git a/app/views/admin/assets/_sort_headers.html.erb b/app/views/admin/assets/_sort_headers.html.erb new file mode 100644 index 00000000..2c8ca234 --- /dev/null +++ b/app/views/admin/assets/_sort_headers.html.erb @@ -0,0 +1,5 @@ +<%= render_sort_bar(true, ['title', 'title','span1-2', 'admin.title'], + ['description', 'description', 'span1-2', 'admin.description'], + ['intro', 'intro', 'span1-2', 'admin.intro'], + ['intro', 'intro', 'span1-2', 'admin.intro'], + ['intro', 'intro', 'span1-2', 'admin.file_length']).html_safe %> diff --git a/app/views/admin/assets/index.html.erb b/app/views/admin/assets/index.html.erb index c31db0c7..7424eb6c 100644 --- a/app/views/admin/assets/index.html.erb +++ b/app/views/admin/assets/index.html.erb @@ -1,32 +1,31 @@ -<% content_for :secondary do %> -

    -
      -
    • <%= link_to t(:new_asset, :scope => :admin), new_admin_asset_path, :remote => true, :class => 'button positive' %>
    • -
    • <%= link_to t('admin.assets.file'), '', :remote => true, :class => 'button positive' %>
    • -
    • <%= link_to t('admin.assets.album'), '', :remote => true, :class => 'button positive'%>
    • -
    • <%= link_to t('admin.assets.video'), '', :remote => true, :class => 'button positive' %>
    • -
    • <%= link_to t('admin.assets.book'), '', :remote => true, :class => 'button positive' %>
    • -
    -
    -<% end -%> - -
    -

    <%= t('admin.list_assets') %>

    - - +<%= form_for :assets, :url => delete_admin_assets_path(:direction => params[:direction], :sort => params[:sort], :filter => @filter, :new_filter => nil, :sort_options => params[:sort_options]), :html => {:id => 'delete_all'}, :remote => true do %> + <%= render 'filter' %> +
    - - + + + + + + + + - + <%= render :partial => 'asset', :collection => @assets %> - +
    <%= t('admin.id') %> - <%= t('admin.file_name') %> - <%= t('admin.description') %> - <%= t('admin.format') %> - <%= t('admin.orig_upload_file') %> - <%= t('admin.file_size') %> - <%= t('admin.action') %> -
    -
    \ No newline at end of file +<% end %> + +
    + <%= link_to content_tag(:i, nil, :class => 'icon-plus icon-white') + t('admin.add'), new_admin_asset_path, :remote => true, :class => 'btn btn-primary pull-right' %> +
    + <%= paginate @assets, :params => {:direction => params[:direction], :sort => params[:sort], :filter => @filter, :new_filter => nil, :sort_options => params[:sort_options]} %> +
    +
    + +<% content_for :page_specific_javascript do %> + <%= javascript_include_tag "/static/jquery.cycle.all.latest.js" %> + <%= javascript_include_tag "inc/modal-preview" %> +<% end %> + diff --git a/config/environments/development.rb b/config/environments/development.rb index 02624772..2dac58e9 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -24,6 +24,9 @@ Orbit::Application.configure do config.action_dispatch.best_standards_support = :builtin + config.assets.debug = true + + # config.middleware.use ExceptionNotifier, # :email_prefix => "[R4_error]", # :sender_address => %{"notifier" }, diff --git a/config/routes.rb b/config/routes.rb index d2294c17..24862361 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,7 +13,12 @@ Orbit::Application.routes.draw do # routes for admin namespace :admin do mount Resque::Server.new, :at => "/resque" - resources :assets + resources :assets do + collection do + post 'delete' + end + end + resources :asset_categories resources :app_auths resources :object_auths do match 'new_interface/:ob_type/:title/new' => "object_auths_new_interface#new" ,:as => :init_ob_auth,:via => :get diff --git a/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb b/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb index 4e3368b2..4ffea341 100644 --- a/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb +++ b/vendor/built_in_modules/announcement/app/views/panel/announcement/back_end/bulletins/_form.html.erb @@ -50,13 +50,13 @@
    - <%= f.date_select :postdate, {:use_month_numbers => true, :order => [:day, :month, :year] }, {:class => 'input-small'} %> + <%= f.datetime_select :postdate, {:use_month_numbers => true, :order => [:day, :month, :year] }, {:class => 'input-small'} %>
    - <%= f.date_select :deadline, {:use_month_numbers => true, :prompt => { :month => 'Month', :day => 'Day', :year => 'Year'}, :order => [:day, :month, :year] }, {:class => 'input-small'} %> + <%= f.datetime_select :deadline, {:use_month_numbers => true, :prompt => { :month => 'Month', :day => 'Day', :year => 'Year'}, :order => [:day, :month, :year] }, {:class => 'input-small'} %>