From 08dc7ecdd3775c54bd531b1a287f72079887b2b4 Mon Sep 17 00:00:00 2001 From: Wojciech Todryk Date: Thu, 21 Jul 2011 20:20:15 +0200 Subject: [PATCH] form scratch --- .gitignore | 10 +- AUTHORS | 0 Gemfile | 6 +- Gemfile.lock | 25 +- README.markdown | 36 +- Rakefile | 0 UNLICENSE | 0 app/controllers/application_controller.rb | 195 +- app/controllers/contact_groups_controller.rb | 56 - app/controllers/contacts_controller.rb | 378 -- app/controllers/core_controller.rb | 18 + app/controllers/folders_controller.rb | 26 - app/controllers/login_controller.rb | 73 - app/controllers/webmail_controller.rb | 424 -- app/helpers/application_helper.rb | 136 - app/helpers/contact_group_helper.rb | 4 - app/helpers/contacts_helper.rb | 3 - app/helpers/core_helper.rb | 2 + app/helpers/folders_helper.rb | 2 - app/helpers/navigation_helper.rb | 60 - app/helpers/webmail_helper.rb | 167 - app/models/contact.rb | 76 - app/models/contact_group.rb | 30 - app/models/customer.rb | 32 - app/models/imap_message.rb | 43 - app/models/mail_pref.rb | 9 - ...> contents_moved_to_olive_theme_directory} | 0 arts/logo2.xcf | Bin arts/logo3.xcf | Bin config.ru | 0 config/application.rb | 25 +- config/boot.rb | 0 config/database.yml.example | 0 config/default_site.rb | 52 - config/defaults.yml | 2 + config/environment.rb | 0 config/environments/development.rb | 0 config/environments/production.rb | 0 config/environments/test.rb | 0 config/initializers/backtrace_silencers.rb | 0 config/initializers/inflections.rb | 0 config/initializers/mime_types.rb | 0 config/initializers/pluralization.rb | 36 - config/initializers/secret_token.rb | 2 +- config/initializers/session_store.rb | 2 +- config/locales/en.yml | 3 + config/locales/pl.yml | 7 +- config/routes.rb | 24 +- db/migrate/20090107193228_init.rb | 66 - db/schema.rb | 96 - db/seeds.rb | 0 doc/README_FOR_APP | 0 lib/cdfutils.rb | 183 - lib/imap_utils.rb | 69 - lib/tasks/.gitkeep | 0 lib/tmail_patch.rb | 38 - lib/webmail/bounced_mail.rb | 8 - lib/webmail/cdfmail.rb | 306 - lib/webmail/environment.rb | 5 - lib/webmail/expression.rb | 2 - lib/webmail/filter.rb | 3 - lib/webmail/imap_message.rb | 38 - lib/webmail/imapmailbox.rb | 526 -- lib/webmail/mail2screen.rb | 171 - lib/webmail/mail_transform.rb | 75 - lib/webmail/maildropserializator.rb | 51 - lib/webmail/routes.rb | 4 - lib/webmail/virtual_email.rb | 3 - public/404.html | 0 public/422.html | 0 public/500.html | 0 public/favicon.ico | 0 public/images/logo3.png | Bin 63579 -> 0 bytes public/images/logo3_dark.png | Bin 77962 -> 0 bytes {themes/original => public}/images/rails.png | Bin public/robots.txt | 0 public/{javascripts => stylesheets}/.gitkeep | 0 test/functional/core_controller_test.rb | 14 + test/performance/browsing_test.rb | 0 test/test_helper.rb | 0 test/unit/helpers/core_helper_test.rb | 4 + themes/olive/README.olive | 0 themes/olive/images/.gitkeep | 0 themes/olive/images/key.png | Bin themes/olive/images/logo_small.png | Bin themes/olive/javascripts/.gitkeep | 0 themes/olive/javascripts/application.js | 2 + themes/olive/javascripts/contact_choose.js | 52 - themes/olive/javascripts/controls.js | 965 +++ themes/olive/javascripts/dragdrop.js | 974 +++ themes/olive/javascripts/effects.js | 1123 +++ themes/olive/javascripts/effects2.js | 349 - themes/olive/javascripts/global.js | 216 - themes/olive/javascripts/global_src.js | 201 - themes/olive/javascripts/htmlstyle.js | 21 - themes/olive/javascripts/jstrim.pl | 76 - themes/olive/javascripts/prototype.js | 6001 +++++++++++++++++ themes/olive/javascripts/prototype_src.js | 521 -- themes/olive/javascripts/rails.js | 191 + themes/olive/javascripts/scriptaculous.js | 47 - themes/olive/javascripts/slider.js | 258 - themes/olive/javascripts/webmail.js | 35 - themes/olive/stylesheets/.gitkeep | 0 themes/olive/stylesheets/admin.css | 554 -- themes/olive/stylesheets/base.css | 24 +- themes/olive/stylesheets/mailr.css | 68 - themes/olive/stylesheets/style.css | 19 +- themes/olive/stylesheets/tabs.css | 111 - .../stylesheets/webmail/icon-folder-open.gif | Bin 155 -> 0 bytes themes/olive/stylesheets/webmail/webmail.css | 161 - .../olive/views/contact_groups/edit.html.erb | 24 - .../olive/views/contact_groups/index.html.erb | 26 - .../views/contacts/add_multiple.html.erb | 26 - themes/olive/views/contacts/choose.html.erb | 11 - .../views/contacts/import_preview.html.erb | 43 - themes/olive/views/contacts/index.html.erb | 115 - themes/olive/views/contacts/new.html.erb | 73 - .../index.html.erb => core/login.html.erb} | 10 +- themes/olive/views/core/logout.html.erb | 2 + themes/olive/views/folders/index.html.erb | 48 - themes/olive/views/layouts/.gitkeep | 0 .../{public.html.erb => application.html.erb} | 0 themes/olive/views/layouts/chooser.html.erb | 16 - .../{login.html.erb => simple.html.erb} | 0 themes/olive/views/shared/_folders.html.erb | 9 - themes/olive/views/shared/_logo.html.erb | 1 - themes/olive/views/shared/_messages.html.erb | 31 - themes/olive/views/shared/_msg_ops.html.erb | 21 - themes/olive/views/webmail/_contacts.html.erb | 12 - themes/olive/views/webmail/_expr.html.erb | 18 - themes/olive/views/webmail/_filter.html.erb | 19 - .../olive/views/webmail/_message_row.html.erb | 14 - themes/olive/views/webmail/_search.html.erb | 13 - themes/olive/views/webmail/compose.html.erb | 67 - .../views/webmail/error_connection.html.erb | 26 - themes/olive/views/webmail/filter.html.erb | 41 - themes/olive/views/webmail/filters.html.erb | 42 - themes/olive/views/webmail/folders.html.erb | 29 - themes/olive/views/webmail/mailsent.html.erb | 31 - themes/olive/views/webmail/message.html.erb | 42 - themes/olive/views/webmail/messages.html.erb | 80 - .../olive/views/webmail/noattachment.html.erb | 1 - themes/olive/views/webmail/prefs.html.erb | 55 - .../olive/views/webmail/view_source.html.erb | 13 - themes/original/images/.gitkeep | 0 themes/original/images/attachment.png | Bin 657 -> 0 bytes themes/original/images/d6deec.gif | Bin 97 -> 0 bytes themes/original/images/deselect.png | Bin 180 -> 0 bytes themes/original/images/list_closed.gif | Bin 84 -> 0 bytes themes/original/images/list_opened.gif | Bin 95 -> 0 bytes themes/original/images/logo.png | Bin 13132 -> 0 bytes themes/original/images/noprogress.gif | Bin 109 -> 0 bytes themes/original/images/select.png | Bin 199 -> 0 bytes themes/original/images/white.gif | Bin 84 -> 0 bytes themes/original/images/white.png | Bin 295 -> 0 bytes themes/original/javascripts/.gitkeep | 0 themes/original/javascripts/contact_choose.js | 52 - themes/original/javascripts/effects2.js | 349 - themes/original/javascripts/global.js | 216 - themes/original/javascripts/global_src.js | 201 - themes/original/javascripts/htmlstyle.js | 21 - themes/original/javascripts/jstrim.pl | 76 - themes/original/javascripts/prototype_src.js | 521 -- themes/original/javascripts/scriptaculous.js | 47 - themes/original/javascripts/slider.js | 258 - themes/original/javascripts/webmail.js | 35 - themes/original/stylesheets/.gitkeep | 0 themes/original/stylesheets/admin.css | 554 -- themes/original/stylesheets/mailr.css | 68 - themes/original/stylesheets/tabs.css | 111 - .../stylesheets/webmail/icon-folder-open.gif | Bin 155 -> 0 bytes .../original/stylesheets/webmail/webmail.css | 161 - .../views/contact_groups/edit.html.erb | 24 - .../views/contact_groups/index.html.erb | 26 - .../views/contacts/add_multiple.html.erb | 26 - .../original/views/contacts/choose.html.erb | 11 - .../views/contacts/import_preview.html.erb | 43 - themes/original/views/contacts/index.html.erb | 115 - themes/original/views/contacts/new.html.erb | 73 - themes/original/views/folders/index.html.erb | 48 - themes/original/views/layouts/.gitkeep | 0 .../original/views/layouts/chooser.html.erb | 19 - themes/original/views/layouts/login.html.erb | 15 - themes/original/views/layouts/public.html.erb | 31 - themes/original/views/login/index.rhtml | 29 - .../original/views/shared/_folders.html.erb | 12 - .../original/views/webmail/_contacts.html.erb | 12 - themes/original/views/webmail/_expr.html.erb | 18 - .../original/views/webmail/_filter.html.erb | 19 - .../views/webmail/_message_row.html.erb | 14 - .../original/views/webmail/_search.html.erb | 13 - .../original/views/webmail/compose.html.erb | 67 - .../views/webmail/error_connection.html.erb | 26 - themes/original/views/webmail/filter.html.erb | 41 - .../original/views/webmail/filters.html.erb | 42 - .../original/views/webmail/folders.html.erb | 29 - .../original/views/webmail/mailsent.html.erb | 31 - .../original/views/webmail/message.html.erb | 42 - .../original/views/webmail/messages.html.erb | 80 - .../views/webmail/noattachment.html.erb | 1 - themes/original/views/webmail/prefs.html.erb | 55 - .../views/webmail/view_source.html.erb | 13 - vendor/ezcrypto-0.1.1/._README | Bin 178 -> 0 bytes vendor/ezcrypto-0.1.1/._rakefile | Bin 178 -> 0 bytes vendor/ezcrypto-0.1.1/MIT-LICENSE | 21 - vendor/ezcrypto-0.1.1/README | 130 - vendor/ezcrypto-0.1.1/lib/ezcrypto.rb | 357 - vendor/ezcrypto-0.1.1/rakefile | 195 - vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb | 112 - vendor/plugins/.gitkeep | 0 vendor/plugins/auto_complete/README | 23 - vendor/plugins/auto_complete/Rakefile | 22 - vendor/plugins/auto_complete/init.rb | 2 - .../auto_complete/lib/auto_complete.rb | 47 - .../lib/auto_complete_macros_helper.rb | 143 - .../auto_complete/test/auto_complete_test.rb | 67 - vendor/plugins/classic_pagination/CHANGELOG | 152 - vendor/plugins/classic_pagination/README | 18 - vendor/plugins/classic_pagination/Rakefile | 22 - vendor/plugins/classic_pagination/init.rb | 33 - vendor/plugins/classic_pagination/install.rb | 1 - .../classic_pagination/lib/pagination.rb | 405 -- .../lib/pagination_helper.rb | 135 - .../test/fixtures/companies.yml | 24 - .../test/fixtures/company.rb | 9 - .../test/fixtures/developer.rb | 7 - .../test/fixtures/developers.yml | 21 - .../test/fixtures/developers_projects.yml | 13 - .../test/fixtures/project.rb | 3 - .../test/fixtures/projects.yml | 7 - .../test/fixtures/replies.yml | 13 - .../classic_pagination/test/fixtures/reply.rb | 5 - .../test/fixtures/schema.sql | 42 - .../classic_pagination/test/fixtures/topic.rb | 3 - .../test/fixtures/topics.yml | 22 - .../plugins/classic_pagination/test/helper.rb | 117 - .../test/pagination_helper_test.rb | 38 - .../test/pagination_test.rb | 177 - 238 files changed, 9393 insertions(+), 13192 deletions(-) mode change 100644 => 100755 AUTHORS mode change 100755 => 100644 Gemfile.lock mode change 100644 => 100755 README.markdown mode change 100755 => 100644 Rakefile mode change 100644 => 100755 UNLICENSE mode change 100755 => 100644 app/controllers/application_controller.rb delete mode 100755 app/controllers/contact_groups_controller.rb delete mode 100755 app/controllers/contacts_controller.rb create mode 100644 app/controllers/core_controller.rb delete mode 100755 app/controllers/folders_controller.rb delete mode 100755 app/controllers/login_controller.rb delete mode 100755 app/controllers/webmail_controller.rb mode change 100755 => 100644 app/helpers/application_helper.rb delete mode 100755 app/helpers/contact_group_helper.rb delete mode 100755 app/helpers/contacts_helper.rb create mode 100644 app/helpers/core_helper.rb delete mode 100755 app/helpers/folders_helper.rb delete mode 100755 app/helpers/navigation_helper.rb delete mode 100755 app/helpers/webmail_helper.rb delete mode 100755 app/models/contact.rb delete mode 100755 app/models/contact_group.rb delete mode 100755 app/models/customer.rb delete mode 100755 app/models/imap_message.rb delete mode 100755 app/models/mail_pref.rb rename app/views/{contents_moved_to_original_theme_directory => contents_moved_to_olive_theme_directory} (100%) mode change 100644 => 100755 mode change 100644 => 100755 arts/logo2.xcf mode change 100644 => 100755 arts/logo3.xcf mode change 100755 => 100644 config.ru mode change 100755 => 100644 config/application.rb mode change 100755 => 100644 config/boot.rb mode change 100644 => 100755 config/database.yml.example delete mode 100755 config/default_site.rb create mode 100644 config/defaults.yml mode change 100755 => 100644 config/environment.rb mode change 100755 => 100644 config/environments/development.rb mode change 100755 => 100644 config/environments/production.rb mode change 100755 => 100644 config/environments/test.rb mode change 100755 => 100644 config/initializers/backtrace_silencers.rb mode change 100755 => 100644 config/initializers/inflections.rb mode change 100755 => 100644 config/initializers/mime_types.rb delete mode 100755 config/initializers/pluralization.rb mode change 100755 => 100644 config/initializers/secret_token.rb mode change 100755 => 100644 config/initializers/session_store.rb mode change 100755 => 100644 config/routes.rb delete mode 100755 db/migrate/20090107193228_init.rb delete mode 100755 db/schema.rb mode change 100755 => 100644 db/seeds.rb mode change 100755 => 100644 doc/README_FOR_APP delete mode 100755 lib/cdfutils.rb delete mode 100755 lib/imap_utils.rb mode change 100755 => 100644 lib/tasks/.gitkeep delete mode 100755 lib/tmail_patch.rb delete mode 100755 lib/webmail/bounced_mail.rb delete mode 100755 lib/webmail/cdfmail.rb delete mode 100755 lib/webmail/environment.rb delete mode 100755 lib/webmail/expression.rb delete mode 100755 lib/webmail/filter.rb delete mode 100755 lib/webmail/imap_message.rb delete mode 100755 lib/webmail/imapmailbox.rb delete mode 100755 lib/webmail/mail2screen.rb delete mode 100755 lib/webmail/mail_transform.rb delete mode 100755 lib/webmail/maildropserializator.rb delete mode 100755 lib/webmail/routes.rb delete mode 100755 lib/webmail/virtual_email.rb mode change 100755 => 100644 public/404.html mode change 100755 => 100644 public/422.html mode change 100755 => 100644 public/500.html mode change 100755 => 100644 public/favicon.ico delete mode 100644 public/images/logo3.png delete mode 100644 public/images/logo3_dark.png rename {themes/original => public}/images/rails.png (100%) mode change 100755 => 100644 mode change 100755 => 100644 public/robots.txt rename public/{javascripts => stylesheets}/.gitkeep (100%) create mode 100644 test/functional/core_controller_test.rb mode change 100755 => 100644 test/performance/browsing_test.rb mode change 100755 => 100644 test/test_helper.rb create mode 100644 test/unit/helpers/core_helper_test.rb mode change 100644 => 100755 themes/olive/README.olive mode change 100644 => 100755 themes/olive/images/.gitkeep mode change 100644 => 100755 themes/olive/images/key.png mode change 100644 => 100755 themes/olive/images/logo_small.png mode change 100644 => 100755 themes/olive/javascripts/.gitkeep create mode 100755 themes/olive/javascripts/application.js delete mode 100755 themes/olive/javascripts/contact_choose.js create mode 100755 themes/olive/javascripts/controls.js create mode 100755 themes/olive/javascripts/dragdrop.js create mode 100755 themes/olive/javascripts/effects.js delete mode 100755 themes/olive/javascripts/effects2.js delete mode 100755 themes/olive/javascripts/global.js delete mode 100755 themes/olive/javascripts/global_src.js delete mode 100755 themes/olive/javascripts/htmlstyle.js delete mode 100755 themes/olive/javascripts/jstrim.pl create mode 100755 themes/olive/javascripts/prototype.js delete mode 100755 themes/olive/javascripts/prototype_src.js create mode 100755 themes/olive/javascripts/rails.js delete mode 100755 themes/olive/javascripts/scriptaculous.js delete mode 100755 themes/olive/javascripts/slider.js delete mode 100755 themes/olive/javascripts/webmail.js mode change 100644 => 100755 themes/olive/stylesheets/.gitkeep delete mode 100755 themes/olive/stylesheets/admin.css mode change 100644 => 100755 themes/olive/stylesheets/base.css delete mode 100755 themes/olive/stylesheets/mailr.css mode change 100644 => 100755 themes/olive/stylesheets/style.css delete mode 100755 themes/olive/stylesheets/tabs.css delete mode 100755 themes/olive/stylesheets/webmail/icon-folder-open.gif delete mode 100755 themes/olive/stylesheets/webmail/webmail.css delete mode 100755 themes/olive/views/contact_groups/edit.html.erb delete mode 100755 themes/olive/views/contact_groups/index.html.erb delete mode 100755 themes/olive/views/contacts/add_multiple.html.erb delete mode 100755 themes/olive/views/contacts/choose.html.erb delete mode 100755 themes/olive/views/contacts/import_preview.html.erb delete mode 100755 themes/olive/views/contacts/index.html.erb delete mode 100755 themes/olive/views/contacts/new.html.erb rename themes/olive/views/{login/index.html.erb => core/login.html.erb} (76%) mode change 100644 => 100755 create mode 100644 themes/olive/views/core/logout.html.erb delete mode 100755 themes/olive/views/folders/index.html.erb mode change 100644 => 100755 themes/olive/views/layouts/.gitkeep rename themes/olive/views/layouts/{public.html.erb => application.html.erb} (100%) delete mode 100755 themes/olive/views/layouts/chooser.html.erb rename themes/olive/views/layouts/{login.html.erb => simple.html.erb} (100%) delete mode 100755 themes/olive/views/shared/_folders.html.erb delete mode 100755 themes/olive/views/shared/_logo.html.erb delete mode 100755 themes/olive/views/shared/_messages.html.erb delete mode 100755 themes/olive/views/shared/_msg_ops.html.erb delete mode 100755 themes/olive/views/webmail/_contacts.html.erb delete mode 100755 themes/olive/views/webmail/_expr.html.erb delete mode 100755 themes/olive/views/webmail/_filter.html.erb delete mode 100755 themes/olive/views/webmail/_message_row.html.erb delete mode 100755 themes/olive/views/webmail/_search.html.erb delete mode 100755 themes/olive/views/webmail/compose.html.erb delete mode 100755 themes/olive/views/webmail/error_connection.html.erb delete mode 100755 themes/olive/views/webmail/filter.html.erb delete mode 100755 themes/olive/views/webmail/filters.html.erb delete mode 100755 themes/olive/views/webmail/folders.html.erb delete mode 100755 themes/olive/views/webmail/mailsent.html.erb delete mode 100755 themes/olive/views/webmail/message.html.erb delete mode 100755 themes/olive/views/webmail/messages.html.erb delete mode 100755 themes/olive/views/webmail/noattachment.html.erb delete mode 100755 themes/olive/views/webmail/prefs.html.erb delete mode 100755 themes/olive/views/webmail/view_source.html.erb delete mode 100644 themes/original/images/.gitkeep delete mode 100755 themes/original/images/attachment.png delete mode 100755 themes/original/images/d6deec.gif delete mode 100755 themes/original/images/deselect.png delete mode 100755 themes/original/images/list_closed.gif delete mode 100755 themes/original/images/list_opened.gif delete mode 100644 themes/original/images/logo.png delete mode 100755 themes/original/images/noprogress.gif delete mode 100755 themes/original/images/select.png delete mode 100755 themes/original/images/white.gif delete mode 100755 themes/original/images/white.png delete mode 100644 themes/original/javascripts/.gitkeep delete mode 100755 themes/original/javascripts/contact_choose.js delete mode 100755 themes/original/javascripts/effects2.js delete mode 100755 themes/original/javascripts/global.js delete mode 100755 themes/original/javascripts/global_src.js delete mode 100755 themes/original/javascripts/htmlstyle.js delete mode 100755 themes/original/javascripts/jstrim.pl delete mode 100755 themes/original/javascripts/prototype_src.js delete mode 100755 themes/original/javascripts/scriptaculous.js delete mode 100755 themes/original/javascripts/slider.js delete mode 100755 themes/original/javascripts/webmail.js delete mode 100755 themes/original/stylesheets/.gitkeep delete mode 100755 themes/original/stylesheets/admin.css delete mode 100755 themes/original/stylesheets/mailr.css delete mode 100755 themes/original/stylesheets/tabs.css delete mode 100755 themes/original/stylesheets/webmail/icon-folder-open.gif delete mode 100755 themes/original/stylesheets/webmail/webmail.css delete mode 100755 themes/original/views/contact_groups/edit.html.erb delete mode 100755 themes/original/views/contact_groups/index.html.erb delete mode 100755 themes/original/views/contacts/add_multiple.html.erb delete mode 100755 themes/original/views/contacts/choose.html.erb delete mode 100755 themes/original/views/contacts/import_preview.html.erb delete mode 100755 themes/original/views/contacts/index.html.erb delete mode 100755 themes/original/views/contacts/new.html.erb delete mode 100755 themes/original/views/folders/index.html.erb delete mode 100644 themes/original/views/layouts/.gitkeep delete mode 100755 themes/original/views/layouts/chooser.html.erb delete mode 100755 themes/original/views/layouts/login.html.erb delete mode 100644 themes/original/views/layouts/public.html.erb delete mode 100755 themes/original/views/login/index.rhtml delete mode 100755 themes/original/views/shared/_folders.html.erb delete mode 100755 themes/original/views/webmail/_contacts.html.erb delete mode 100755 themes/original/views/webmail/_expr.html.erb delete mode 100755 themes/original/views/webmail/_filter.html.erb delete mode 100755 themes/original/views/webmail/_message_row.html.erb delete mode 100755 themes/original/views/webmail/_search.html.erb delete mode 100755 themes/original/views/webmail/compose.html.erb delete mode 100755 themes/original/views/webmail/error_connection.html.erb delete mode 100755 themes/original/views/webmail/filter.html.erb delete mode 100755 themes/original/views/webmail/filters.html.erb delete mode 100755 themes/original/views/webmail/folders.html.erb delete mode 100755 themes/original/views/webmail/mailsent.html.erb delete mode 100755 themes/original/views/webmail/message.html.erb delete mode 100755 themes/original/views/webmail/messages.html.erb delete mode 100755 themes/original/views/webmail/noattachment.html.erb delete mode 100755 themes/original/views/webmail/prefs.html.erb delete mode 100755 themes/original/views/webmail/view_source.html.erb delete mode 100755 vendor/ezcrypto-0.1.1/._README delete mode 100755 vendor/ezcrypto-0.1.1/._rakefile delete mode 100755 vendor/ezcrypto-0.1.1/MIT-LICENSE delete mode 100755 vendor/ezcrypto-0.1.1/README delete mode 100755 vendor/ezcrypto-0.1.1/lib/ezcrypto.rb delete mode 100755 vendor/ezcrypto-0.1.1/rakefile delete mode 100755 vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb mode change 100755 => 100644 vendor/plugins/.gitkeep delete mode 100755 vendor/plugins/auto_complete/README delete mode 100755 vendor/plugins/auto_complete/Rakefile delete mode 100755 vendor/plugins/auto_complete/init.rb delete mode 100755 vendor/plugins/auto_complete/lib/auto_complete.rb delete mode 100755 vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb delete mode 100755 vendor/plugins/auto_complete/test/auto_complete_test.rb delete mode 100755 vendor/plugins/classic_pagination/CHANGELOG delete mode 100755 vendor/plugins/classic_pagination/README delete mode 100755 vendor/plugins/classic_pagination/Rakefile delete mode 100755 vendor/plugins/classic_pagination/init.rb delete mode 100755 vendor/plugins/classic_pagination/install.rb delete mode 100755 vendor/plugins/classic_pagination/lib/pagination.rb delete mode 100755 vendor/plugins/classic_pagination/lib/pagination_helper.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/companies.yml delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/company.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/developer.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/developers.yml delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/project.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/projects.yml delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/replies.yml delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/reply.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/schema.sql delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/topic.rb delete mode 100755 vendor/plugins/classic_pagination/test/fixtures/topics.yml delete mode 100755 vendor/plugins/classic_pagination/test/helper.rb delete mode 100755 vendor/plugins/classic_pagination/test/pagination_helper_test.rb delete mode 100755 vendor/plugins/classic_pagination/test/pagination_test.rb diff --git a/.gitignore b/.gitignore index d6ea040..9a3eba8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ -log +.bundle +db/*.sqlite3 +log/*.log +tmp/ config/database.yml -.*.sw? -config/site.rb -tmp -mail_temp -.svn diff --git a/AUTHORS b/AUTHORS old mode 100644 new mode 100755 diff --git a/Gemfile b/Gemfile index 79bbe16..a61cdfc 100755 --- a/Gemfile +++ b/Gemfile @@ -5,12 +5,12 @@ gem 'rails', '3.0.7' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' -gem 'sqlite3-ruby',:require => 'sqlite3' +#gem 'sqlite3-ruby',:require => 'sqlite3' gem 'arel' -gem 'mysql2' +gem 'mysql2' , '0.2.7' gem 'will_paginate' gem 'themes_for_rails' -gem 'tmail' +#gem 'tmail' # Use unicorn as the web server # gem 'unicorn' diff --git a/Gemfile.lock b/Gemfile.lock old mode 100755 new mode 100644 index 1f3e0d8..5c1753f --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,20 +27,21 @@ GEM activemodel (= 3.0.7) activesupport (= 3.0.7) activesupport (3.0.7) - arel (2.0.4) + arel (2.0.10) builder (2.1.2) erubis (2.7.0) i18n (0.5.0) - mail (2.2.18) + mail (2.2.19) activesupport (>= 2.3.6) i18n (>= 0.4.0) - mime-types (>= 1.16) - treetop (>= 1.4.8) + mime-types (~> 1.16) + treetop (~> 1.4.8) mime-types (1.16) mysql2 (0.2.7) polyglot (0.3.1) - rack (1.2.2) - rack-mount (0.7.3) + rack (1.2.3) + rack-mount (0.8.1) + rack (>= 1.0.0) rack-test (0.5.7) rack (>= 1.0) rails (3.0.7) @@ -56,18 +57,14 @@ GEM activesupport (= 3.0.7) rake (>= 0.8.7) thor (~> 0.14.4) - rake (0.8.7) - sqlite3 (1.3.3) - sqlite3-ruby (1.3.3) - sqlite3 (>= 1.3.3) + rake (0.9.2) themes_for_rails (0.4.2) rails (~> 3.0.0) themes_for_rails thor (0.14.6) - tmail (1.2.7.1) treetop (1.4.9) polyglot (>= 0.3.1) - tzinfo (0.3.24) + tzinfo (0.3.29) will_paginate (2.3.15) PLATFORMS @@ -75,9 +72,7 @@ PLATFORMS DEPENDENCIES arel - mysql2 + mysql2 (= 0.2.7) rails (= 3.0.7) - sqlite3-ruby themes_for_rails - tmail will_paginate diff --git a/README.markdown b/README.markdown old mode 100644 new mode 100755 index e63e407..4cb73d5 --- a/README.markdown +++ b/README.markdown @@ -1,8 +1,6 @@ ## Introduction _Mailr_ is a IMAP mail client based on _Ruby on Rails_ platform. -## Installation guide - **NOTE** All path and filenames are based on _Rails.root_ directory. ### Requirements @@ -13,35 +11,17 @@ In _Rails 3_ all dependencies should be defined in file _Gemfile_. All needed ge * Checkout the source code. -* If you need to override some of the default constants used in the application take a look at _config/default_site.rb_. Then create _config/site.rb_ that contains only the keys which you want to override. Example content of _config/site.rb_ is: - -```ruby -module CDF - -LOCALCONFIG = { - :imap_server => 'your.imap.server' -} -end -``` - -* Configure SMTP settings - -```ruby -# initializers/smtp_settings.rb - ActionMailer::Base.smtp_settings = { - :address => "mail.example.com.py", - :port => 26, - :authentication => :plain, - :enable_starttls_auto => true, - :user_name => "emilio@example.com.py", - :password => "yourpass" -} -``` +* Install all dependiences. Use _bundler_ for that. * Prepare config/database.yml file (see _config/database.yml.example_). - Check if proper gems (sqlite3/mysql/postgresql) are defined in _Gemfile_ and installed. + Check if proper gems (sqlite3/mysql/postgresql) are defined in _Gemfile_ and installed. * Migrate database (rake db:migrate) -* Use it. +* Start rails server if applicable +* Point Your browser to application URL: + For local access: http://localhost:3000 + For remote access: http://some_url/mailr + +* Use it. diff --git a/Rakefile b/Rakefile old mode 100755 new mode 100644 diff --git a/UNLICENSE b/UNLICENSE old mode 100644 new mode 100755 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb old mode 100755 new mode 100644 index 279d8db..0ad5869 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,189 +1,24 @@ -# The filters added to this controller will be run for all controllers in the application. -# Likewise will all the methods added be available for all controllers. +require 'yaml' + class ApplicationController < ActionController::Base - protect_from_forgery + protect_from_forgery - before_filter :user_login_filter - before_filter :add_scripts - #before_filter :localize + before_filter :load_defaults + before_filter :set_locale + protected - #filter_parameter_logging :password #upgrade to Rails3 + def load_defaults + @defaults = YAML::load(File.open(Rails.root.join('config','defaults.yml'))) + end - protected - - def theme_resolver - CDF::CONFIG[:theme] || CDF::CONFIG[:default_theme] - end - - def secure_user?() true end - def secure_cust?() false end - def additional_scripts() "" end - def onload_function() "" end - - private - def add_scripts - @additional_scripts = additional_scripts() - @onload_function = onload_function() - end - - def user_login_filter - if (secure_user? or secure_cust? )and logged_user.nil? - - #upgrade Rails 3 - #session["return_to"] = request.request_uri - logger.debug "*** return_to => #{request.fullpath}" - session["return_to"] = request.fullpath - - redirect_to :controller=>"/login", :action => "index" - return false - end - end - - alias login_required user_login_filter - - def logged_user # returns customer id - session['user'] - end - - def logged_customer - session['user'] - end - - def localize - # We will use instance vars for the locale so we can make use of them in - # the templates. - @charset = 'utf-8' - headers['Content-Type'] = "text/html; charset=#{@charset}" - # Here is a very simplified approach to extract the prefered language - # from the request. If all fails, just use 'en_EN' as the default. - temp = if request.env['HTTP_ACCEPT_LANGUAGE'].nil? - [] - else - request.env['HTTP_ACCEPT_LANGUAGE'].split(',').first.split('-') rescue [] - end - language = temp.slice(0) - dialect = temp.slice(1) - @language = language.nil? ? 'en' : language.downcase # default is en - # If there is no dialect use the language code ('en' becomes 'en_EN'). - @dialect = dialect.nil? ? @language.upcase : dialect - # The complete locale string consists of - # language_DIALECT (en_EN, en_GB, de_DE, ...) - @locale = "#{@language}_#{@dialect.upcase}" - @htmllang = @language == @dialect ? @language : "#{@language}-#{@dialect}" - # Finally, bind the textdomain to the locale. From now on every used - # _('String') will get translated into the right language. (Provided - # that we have a corresponding mo file in the right place). - bindtextdomain('messages', "#{RAILS_ROOT}/locale", @locale, @charset) - end - - public - - def include_tinymce(mode="textareas",elements="") - tinymce='' - tinymce << ' - - ' - tinymce - end - - helper_method :include_tinymce - - def include_simple_tinymce(mode="textareas",elements="") - tinymce = '' - tinymce << ' - ' - tinymce - end - - def _(text) - t text - end - - helper_method :include_simple_tinymce, :_ + def theme_resolver + @defaults['theme'] + end + def set_locale + I18n.locale = @defaults['locale'] || I18n.default_locale + end end diff --git a/app/controllers/contact_groups_controller.rb b/app/controllers/contact_groups_controller.rb deleted file mode 100755 index b09a1b6..0000000 --- a/app/controllers/contact_groups_controller.rb +++ /dev/null @@ -1,56 +0,0 @@ -class ContactGroupsController < ApplicationController - - theme :theme_resolver - - layout 'public' - - - def index - @contact_group = ContactGroup.new - @contact_group.customer_id = logged_user - @contactgroups = ContactGroup.find_by_user(logged_user) - end - - def add - @contactgroup = ContactGroup.new - @contactgroup.customer_id = logged_user - render("/contact_group/edit") - end - - def delete - contactgroup = ContactGroup.find(@params["id"]) - contactgroup.destroy - redirect_to(:action=>"list") - end - - def edit - @contactgroup = ContactGroup.find(@params["id"]) - end - - def save - begin - if @params["contactgroup"]["id"].nil? or @params["contactgroup"]["id"] == "" - # New contactgroup - @contactgroup = ContactGroup.create(@params["contactgroup"]) - else - # Edit existing - @contactgroup = ContactGroup.find(@params["contactgroup"]["id"]) - @contactgroup.attributes = @params["contactgroup"] - end - - if @contactgroup.save - redirect_to(:action=>"list") - else - render "/contact_group/edit" - end - rescue CDF::ValidationError => e - logger.info("RESCUE") - @contactgroup = e.entity - render("/contact_group/edit") - end - end - - protected - def secure_user?() true end - -end diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb deleted file mode 100755 index ace89c3..0000000 --- a/app/controllers/contacts_controller.rb +++ /dev/null @@ -1,378 +0,0 @@ -class ContactsController < ApplicationController - - theme :theme_resolver - - layout :select_layout - - def index - if params[:letter] && params[:letter].any? - @contacts = Contact.for_customer(logged_user).letter(params[:letter]).paginate :page => params[:page], - :per_page => CDF::CONFIG[:contacts_per_page] - else - @contacts = Contact.for_customer(logged_user).paginate :page => params[:page], :per_page => CDF::CONFIG[:contacts_per_page] - end - end - - def listLetter - letters = CDF::CONFIG[:contact_letters] - @contact_pages = Paginator.new(self, Contact.count( - ["customer_id = %s and substr(UPPER(fname),1,1) = '%s'", logged_user, letters[params['id'].to_i]]), CDF::CONFIG[:contacts_per_page], params['page']) - @contacts = Contact.find(:all, :conditions=>["customer_id = %s and substr(UPPER(fname),1,1) = '%s'", logged_user, letters[params['id'].to_i]], - :order=>['fname'], :limit=>CDF::CONFIG[:contacts_per_page], :offset=>@contact_pages.current.offset) - - if params["mode"] == "groups" - if params["group_id"] and not params["group_id"].nil? and not params["group_id"] == '' - @group_id = params["group_id"].to_i - @contacts_for_group = Hash.new - for contact in @contacts - @contacts_for_group[contact.id] = 0 # initialize - for gr in contact.groups - if gr.contact_group_id.to_i == @group_id - @contacts_for_group[contact.id] = 1 # checked - end - end - end - end - end - - render :action => "list" - end - - def new - @contact = Contact.new - @contact.customer_id = logged_user - - # load related lists - loadLists - - # Init groups: because of checkbox - # Set all to 0 => unchecked - @groups = Hash.new - @contactgroups.each {|g| - @groups[g.id] = 0 - } - end - - def add_multiple - @contact = Contact.new - @contact["file_type"] = "1" - end - - def add_from_mail - cstr = params['cstr'] - retmsg = params['retmsg'] - session["return_to"] = url_for(:controller=>'/webmail/webmail', - :action=>'folders', - :msg_id=>retmsg) - # parse string - if i = cstr.index("<") - name, email = cstr.slice(0, i), cstr.slice((i+1)..(cstr.strip().index(">")-1)) - fname = name.split().first - lname = name.split().last if name.split().size() > 1 - else - fname, lname, email = "", "", cstr - end - - if @contact = Contact.find_by_user_email(logged_user, email) - # load related lists - loadLists - - @contact.fname, @contact.lname = fname, lname - - # groups = @contact.groups - @groups = Hash.new - @contactgroups.each {|g| - groupSelected = false - @contact.groups.each {|gr| - if gr.contact_group_id.to_i == g.id.to_i - groupSelected = true - break - end - } - if groupSelected - @groups[g.id] = 1 # checked - else - @groups[g.id] = 0 # unchecked - end - } - else - @contact = Contact.new("fname"=>fname, "lname" => lname, "email" => email) - @contact.customer_id = logged_user - - # load related lists - loadLists - - # Init groups: because of checkbox - # Set all to 0 => unchecked - @groups = Hash.new - @contactgroups.each {|g| - @groups[g.id] = 0 - } - end - render :action => "new" - end - - def import_preview - file = params["contact"]["data"] - - flash["errors"] = Array.new - - if file.size == 0 - flash["errors"] << _('You haven\'t selected file or the file is empty') - @contact = Contact.new - @contact["file_type"] = params["contact"]["file_type"] - render :action => "add_multiple" - end - - file_type = params["contact"]["file_type"] - if file_type.nil? or file_type == '1' - separator = ',' - else - separator = /\t/ - - end - - @contacts = Array.new - emails = Array.new - - file.each {|line| - cdata = line.strip.chomp.split(separator) - cont = Contact.new - cont.fname = cdata[0].to_s.strip.chomp - cont.lname = cdata[1].to_s.strip.chomp - cont.email = cdata[2].to_s.strip.chomp - - # Check for duplicate emails in the file - if emails.include?(cont.email) - flash["errors"] << sprintf(_('Contact %'), file.lineno.to_s) + ": " + _('The e-mail duplicates the e-mail of another record!') - else - emails << cont.email - end - - @contacts << cont - } - - end - - def import - contacts_count = params["contact"].length - contacts_to_import = params["contact"] - @contacts = Array.new - emails = Array.new - - flash["errors"] = Array.new - - for i in 0...contacts_count - contact = Contact.new - contact.customer_id = logged_user - contact.fname = contacts_to_import[i.to_s]["fname"] - contact.lname = contacts_to_import[i.to_s]["lname"] - contact.email = contacts_to_import[i.to_s]["email"] - - begin - # Check for duplicate emails in the submitted data - if emails.include?(contact.email) - flash["errors"] << sprintf(_('Contact %'), (i+1).to_s) + ": " + _('The e-mail duplicates the e-mail of another record!') - else - emails << contact.email - end - # Check if contact is valid - contact.valid? - rescue CDF::ValidationError => e - if not contact.errors.empty? - ["fname", "lname", "email"].each do |attr| - attr_errors = contact.errors.on(attr) - attr_errors = [attr_errors] unless attr_errors.nil? or attr_errors.is_a? Array - - if not attr_errors.nil? - attr_errors.each do |msg| - flash["errors"] << l(:contact_addmultiple_errorforcontact, (i+1).to_s) + ": " + l(msg) - end - end - end - end - end # rescue - - @contacts << contact - end # for - - # If there are validation errors - display them - if not flash["errors"].nil? and not flash["errors"].empty? - render :action => "import_preview" - else - # save - begin - for contact in @contacts - Contact.create(contact.attributes) - end - # Set message for successful import - flash["alert"] = Array.new - flash["alert"] << l(:contact_addmultiple_success, @contacts.length.to_s) - keep_flash() - redirect_to(:action=>"list") - rescue Exception => exc - flash["errors"] << exc - render :action => "import_preview" - end - end - end - - - def choose - if params["mode"] == "groups" - save_groups - end - - @tos, @ccs, @bccs = Array.new, Array.new, Array.new - - params["contacts_to"].each{ |id,value| @tos << Contact.find(id) if value == "1" } if params["contacts_to"] - params["contacts_cc"].each{ |id,value| @ccs << Contact.find(id) if value == "1" } if params["contacts_cc"] - params["contacts_bcc"].each{ |id,value| @bccs << Contact.find(id) if value == "1" } if params["contacts_bcc"] - - params["groups_to"].each{ |id,value| - ContactGroup.find(id).contacts.each {|c| @tos << c} if value == "1" } if params["groups_to"] - params["groups_cc"].each{ |id,value| - ContactGroup.find(id).contacts.each {|c| @ccs << c} if value == "1" } if params["groups_cc"] - params["groups_bcc"].each{ |id,value| - ContactGroup.find(id).contacts.each {|c| @bccs << c} if value == "1" } if params["groups_bcc"] - end - - def save_groups - contacts_for_group = params["contacts_for_group"] - group_id = params["group_id"] - contact_group = ContactGroup.find(group_id) - - - contacts_for_group.each { |contact_id,value| - contact = Contact.find(contact_id) - if value == "1" and not contact_group.contacts.include?(contact) - contact_group.contacts << contact - end - if value == "0" and contact_group.contacts.include?(contact) - contact_group.contacts.delete(contact) - end - } - redirect_to(:action=>"index", :id=>group_id, :params=>{"mode"=>params["mode"]}) - end - - def edit - @contact = Contact.find(params["id"]) - # load related lists - loadLists - - # groups = @contact.groups - @groups = Hash.new - @contactgroups.each {|g| - groupSelected = false - @contact.groups.each {|gr| - if gr.contact_group_id.to_i == g.id.to_i - groupSelected = true - break - end - } - if groupSelected - @groups[g.id] = 1 # checked - else - @groups[g.id] = 0 # unchecked - end - } - render :action => "new" - end - - # Insert or update - def create - if params["contact"]["id"] == "" - # New contact - @contact = Contact.create(params["contact"]) - else - # Edit existing - @contact = Contact.find(params["contact"]["id"]) - @contact.attributes = params["contact"] - end - - @contactgroups = ContactGroup.find_by_user(logged_user) - # Groups displayed - groups = params['groups'] - tempGroups = Array.new - tempGroups.concat(@contact.groups) - - @contactgroups.each { |cgroup| - includesCGroup = false - tempGroups.each {|gr| - if gr.contact_group_id.to_i == cgroup.id.to_i - includesCGroup = true - break - end - } - if groups["#{cgroup.id}"] == "1" and not includesCGroup - @contact.groups << cgroup - end - - if groups["#{cgroup.id}"] == "0" and includesCGroup - @contact.groups.delete(cgroup) - end - } - if @contact.save - if params["paction"] == t(:save) - redirect_to :action =>:index - else - redirect_to :action => :new - end - else - loadLists - @groups = Hash.new - @contactgroups.each {|g| - if @contact.groups.include?(g) - @groups[g.id] = 1 - else - @groups[g.id] = 0 - end - } - render :action => :new - end - end - - def delete - Contact.destroy(params['id']) - redirect_to(:action=>'index') - end - - protected - def secure_user?() true end - def additional_scripts() - add_s = '' - if action_name == "choose" - add_s<<'' - add_s<<'' - end - add_s - end - - def onload_function() - if action_name == "choose" - "javascript:respondToCaller();" - else - "" - end - end - private - def select_layout - if params["mode"] == "choose" - @mode = "choose" - @contactgroups = ContactGroup.find_by_user(logged_user) - 'chooser' - elsif params["mode"] == "groups" - @mode = "groups" - 'public' - else - @mode = "normal" - 'public' - end - end - - def loadLists - if @contactgroups.nil? - @contactgroups = ContactGroup.find_by_user(logged_user) - end - end -end diff --git a/app/controllers/core_controller.rb b/app/controllers/core_controller.rb new file mode 100644 index 0000000..5b51e6e --- /dev/null +++ b/app/controllers/core_controller.rb @@ -0,0 +1,18 @@ +class CoreController < ApplicationController + + theme :theme_resolver + layout "simple" + + def login + end + + def logout + reset_session + flash[:notice] = t(:user_logged_out) + redirect_to :action => "login" + end + + def authenticate + end + +end diff --git a/app/controllers/folders_controller.rb b/app/controllers/folders_controller.rb deleted file mode 100755 index 5a542fa..0000000 --- a/app/controllers/folders_controller.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'ezcrypto' -class FoldersController < ApplicationController - include ImapUtils - - before_filter :login_required - before_filter :load_imap_session - after_filter :close_imap_session - - theme :theme_resolver - - layout 'public' - - def index - @folders = @mailbox.folders - end - - def create - @mailbox.create_folder(CDF::CONFIG[:mail_inbox] + '.' + params[:folder]) - redirect_to folders_path - end - - def destroy - @mailbox.delete_folder params[:id] - redirect_to folders_path - end -end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb deleted file mode 100755 index fc0f2a6..0000000 --- a/app/controllers/login_controller.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'ezcrypto' -require 'imapmailbox' - -class LoginController < ApplicationController - - theme :theme_resolver - - def index - if not(logged_user.nil?) - redirect_to :controller =>"webmail", :action=>"index" - else - @login_user = Customer.new - end - end - - def authenticate - if user = auth(params['login_user']["email"], params['login_user']["password"]) - session["user"] = user.id - if CDF::CONFIG[:crypt_session_pass] - session["wmp"] = EzCrypto::Key.encrypt_with_password(CDF::CONFIG[:encryption_password], CDF::CONFIG[:encryption_salt], params['login_user']["password"]) - else - # dont use crypt - session["wmp"] = params['login_user']["password"] - end - if session["return_to"] - redirect_to(session["return_to"]) - session["return_to"] = nil - else - redirect_to :action=>"index" - end - else - logger.debug "*** Not logged" - @login_user = Customer.new - flash["error"] = t :wrong_email_or_password - redirect_to :action => "index" - end - end - - def logout - reset_session - flash["status"] = t(:user_logged_out) - redirect_to :action => "index" - end - - protected - - def need_subdomain?() true end - def secure_user?() false end - - private - - def auth(email, password) - mailbox = IMAPMailbox.new(Rails.logger) - logger.info "*** mailbox #{mailbox.inspect}" - begin - mailbox.connect(email, password) - rescue Exception => exc - logger.debug "*** auth/Mailbox Object => #{exc.message}" - return nil - end - - mailbox.disconnect - mailbox = nil - if user = Customer.find_by_email(email) - return user - else - # create record in database - user = Customer.create("email"=>email) - MailPref.create('customer_id' => user.id) - return user - end - end -end diff --git a/app/controllers/webmail_controller.rb b/app/controllers/webmail_controller.rb deleted file mode 100755 index e780276..0000000 --- a/app/controllers/webmail_controller.rb +++ /dev/null @@ -1,424 +0,0 @@ -require 'cdfmail' -require 'net/smtp' -require 'net/imap' -require 'mail2screen' -require 'ezcrypto' -require 'imapmailbox' -require 'imap_utils' - - -class WebmailController < ApplicationController - include ImapUtils - - theme :theme_resolver - - logger.info "*** WebmailController #{logger.inspect}" - - # Administrative functions - before_filter :login_required - before_filter :obtain_cookies_for_search_and_nav, :only=>[:messages] - before_filter :load_imap_session - after_filter :close_imap_session - - layout "public", :except => [:view_source, :download] - -# model :filter, :expression, :mail_pref, :customer - - BOOL_ON = "on" - - def index - redirect_to(:action=>"messages") - end - - def error_connection - end - - def refresh - @mailbox.reload - @folders = @mailbox.folders - redirect_to(:action=>'messages') - end - - def messages - session["return_to"] = nil - @search_field = params['search_field'] - @search_value = params['search_value'] - - # handle sorting - tsort session field contains last reverse or no for field - # and lsort - last sort field - if session['tsort'].nil? or session['lsort'].nil? - session['lsort'] = "DATE" - session['tsort'] = {"DATE" => true, "FROM" => true, "SUBJECT" => true, "TO" => false} - end - - case operation_param - when t(:copy) # copy - msg_ids = [] - messages_param.each { |msg_id, bool| - msg_ids << msg_id.to_i if bool == BOOL_ON and dst_folder != @folder_name } if messages_param - folder.copy_multiple(msg_ids, dst_folder) if msg_ids.size > 0 - when t(:move) # move - msg_ids = [] - messages_param.each { |msg_id, bool| - msg_ids << msg_id.to_i if bool == BOOL_ON and dst_folder != @folder_name } if messages_param - folder.move_multiple(msg_ids, dst_folder) if msg_ids.size > 0 - when t(:delete) # delete - msg_ids = [] - messages_param.each { |msg_id, bool| msg_ids << msg_id.to_i if bool == BOOL_ON } if messages_param - folder.delete_multiple(msg_ids) if msg_ids.size > 0 - when t(:mark_read) # mark as read - messages_param.each { |msg_id, bool| msg = folder.mark_read(msg_id.to_i) if bool == BOOL_ON } if messages_param - when t(:mark_unread) # mark as unread - messages_param.each { |msg_id, bool| msg = folder.mark_unread(msg_id.to_i) if bool == BOOL_ON } if messages_param - when "SORT" - session['lsort'] = sort_query = params["scc"] - session['tsort'][sort_query] = (session['tsort'][sort_query]? false : true) - @search_field, @search_value = session['search_field'], session['search_value'] - when t(:search) # search - session['search_field'] = @search_field - session['search_value'] = @search_value - when t(:show_all) # search - session['search_field'] = @search_field = nil - session['search_value'] = @search_value = nil - else - # get search criteria from session - @search_field = session['search_field'] - @search_value = session['search_value'] - end - - sort_query = session['lsort'] - reverse_sort = session['tsort'][sort_query] - query = ["ALL"] - @page = params["page"] - @page ||= session['page'] - session['page'] = @page - if @search_field and @search_value and not(@search_field.strip() == "") and not(@search_value.strip() == "") - @pages = Paginator.new self, 0, get_mail_prefs.wm_rows, @page - @messages = folder.messages_search([@search_field, @search_value], sort_query + (reverse_sort ? ' desc' : ' asc')) - else - @pages = Paginator.new self, folder.total, get_mail_prefs.wm_rows, @page - @messages = folder.messages(@pages.current.first_item - 1, get_mail_prefs.wm_rows, sort_query + (reverse_sort ? ' desc' : ' asc')) - end - - end - - def delete - @msg_id = msg_id_param.to_i - folder.delete(@msg_id) - redirect_to(:action=>"messages") - end - - def reply # not ready at all - @msg_id = msg_id_param.to_i - @imapmail = folder.message(@msg_id) - fb = @imapmail.full_body - @tmail = TMail::Mail.parse(fb) - - @mail = prepare_mail - @mail.reply(@tmail, fb, get_mail_prefs.mail_type) - - render :action => 'compose' - end - - def forward - @msg_id = msg_id_param.to_i - @imapmail = folder.message(@msg_id) - fb = @imapmail.full_body - @tmail = TMail::Mail.parse(fb) - - @mail = prepare_mail - @mail.forward(@tmail, fb) - - render :action => 'compose' - end - - def compose - if @mail.nil? - operation = operation_param - if operation == t(:send) - @mail = create_mail - encmail = @mail.send_mail - get_imap_session - @mailbox.message_sent(encmail) - - # delete temporary files (attachments) - @mail.delete_attachments() - render :action => :mailsent - elsif operation == t(:add) - @mail = create_mail - if params['attachment'] - attachment = CDF::Attachment.new(@mail) - attachment.file = params['attachment'] - end - else - # default - new email create - @mail = create_mail - end - end - end - - def empty # empty trash folder (works for any one else :-)) - folder.messages(0, -1).each{ |message| - folder.delete(message) - } - folder.expunge - redirect_to(:action=>"messages") - end - - def message - @msg_id = msg_id_param - @imapmail = folder.message(@msg_id) - folder.mark_read(@imapmail.uid) if @imapmail.unread - @mail = TMail::Mail.parse(@imapmail.full_body) - end - - def download - msg_id = msg_id_param - imapmail = folder.message(msg_id) - mail = TMail::Mail.parse(imapmail.full_body) - - if mail.multipart? - get_parts(mail).each { |part| - return send_part(part) if part.header and part.header['content-type']['name'] == params['ctype'] - } - render("webmail/noattachment") - else - render("webmail/noattachment") - end - end - - def prefs - @customer = Customer.find(logged_customer) - @mailpref = MailPref.find_or_create_by_customer_id logged_customer - - if params['op'] == _('Save') - if params['customer'] - @customer.fname = params['customer']['fname'] - @customer.lname = params['customer']['lname'] - @customer.save - end - @mailpref.attributes = params["mailpref"] - @mailpref.save - session["wmimapseskey"] = nil - redirect_to(:action=>"messages") - end - end - - # Message filters management - def filters - end - - def filter - if params['op'] - @filter = Filter.new(params['filter']) - @filter.customer_id = logged_customer - params['expression'].each { |index, expr| @filter.expressions << Expression.new(expr) unless expr["expr_value"].nil? or expr["expr_value"].strip == "" } - case params['op'] - when _('Add') - @filter.expressions << Expression.new - when _('Save') - if params['filter']['id'] and params['filter']['id'] != "" - @sf = Filter.find(params['filter']['id']) - @sf.name, @sf.destination_folder = @filter.name, @filter.destination_folder - @sf.expressions.each{|expr| Expression.delete(expr.id) } - @filter.expressions.each {|expr| @sf.expressions << Expression.create(expr.attributes) } - else - @sf = Filter.create(@filter.attributes) - @sf.order_num = @user.filters.size - @filter.expressions.each {|expr| @sf.expressions << Expression.create(expr.attributes) } - end - # may be some validation will be needed - @sf.save - @user.serialize_to_file - return redirect_to(:action=>"filters") - end - @expressions = @filter.expressions - else - @filter = Filter.find(params["id"]) if params["id"] - @expressions = @filter.expressions - end - @destfolders = get_to_folders - end - - def filter_delete - Filter.delete(params["id"]) - # reindex other filters - @user = Customer.find(logged_customer) - findex = 0 - @user.filters.each { |filter| - findex = findex + 1 - filter.order_num = findex - filter.save - } - @user.serialize_to_file - redirect_to :action=>"filters" - end - - def filter_up - filt = @user.filters.find(params['id']) - ufilt = @user.filters.find_all("order_num = #{filt.order_num - 1}").first - ufilt.order_num = ufilt.order_num + 1 - filt.order_num = filt.order_num - 1 - ufilt.save - filt.save - @user.serialize_to_file - redirect_to :action=>"filters" - end - - def filter_down - filt = Filter.find(params["id"]) - dfilt = @user.filters[filt.order_num] - dfilt.order_num = dfilt.order_num - 1 - filt.order_num = filt.order_num + 1 - dfilt.save - filt.save - @user.serialize_to_file - redirect_to :action=>"filters" - end - - def filter_add - @filter = Filter.new - @filter.expressions << Expression.new - @expressions = @filter.expressions - @destfolders = get_to_folders - render "filter" - end - # end of filters - - def view_source - @msg_id = msg_id_param.to_i - @imapmail = folder.message(@msg_id) - @msg_source = CGI.escapeHTML(@imapmail.full_body).gsub("\n", "
") - end - - def auto_complete_for_mail_to - auto_complete_responder_for_contacts params[:mail][:to] - end - - def auto_complete_for_mail_cc - auto_complete_responder_for_contacts params[:mail][:cc] - end - - def auto_complete_for_mail_bcc - auto_complete_responder_for_contacts params[:mail][:bcc] - end - - private - - def auto_complete_responder_for_contacts(value) - # first split by "," and take last name - searchName = value.split(',').last.strip - - # if there are 2 names search by them - if searchName.split.size > 1 - fname, lname = searchName.split.first, searchName.split.last - conditions = ['customer_id = ? and LOWER(fname) LIKE ? and LOWER(lname) like ?', logged_customer, fname.downcase + '%', lname.downcase + '%'] - else - conditions = ['customer_id = ? and LOWER(fname) LIKE ?', logged_customer, searchName.downcase + '%'] - end - @contacts = Contact.find(:all, :conditions => conditions, :order => 'fname ASC',:limit => 8) - render :partial => 'contacts' - end - - protected - - def additional_scripts() - @additional_css = ["webmail/webmail"] - @additional_js = ["webmail"] - end - - private - - def get_to_folders - res = Array.new - @folders.each{|f| res << f unless f.name == CDF::CONFIG[:mail_sent] or f.name == CDF::CONFIG[:mail_inbox] } - res - end - - - def create_mail - m = CDF::Mail.new(user.mail_temporary_path) - if params["mail"] - ma = params["mail"] - m.body, m.content_type, m.from, m.to, m.cc, m.bcc, m.subject = ma["body"], ma["content_type"], ma["from"], ma["to"], ma["cc"], ma["bcc"], ma["subject"] - if params["att_files"] - att_files, att_tfiles, att_ctypes = params["att_files"], params["att_tfiles"], params["att_ctypes"] - att_files.each {|i, value| - att = CDF::Attachment.new(m) - att.filename, att.temp_filename, att.content_type = value, att_tfiles[i], att_ctypes[i] - } - end - else - m.from, m.content_type = user.friendlly_local_email, get_mail_prefs.mail_type - end - m.customer_id = logged_customer - m - end - - def prepare_mail - m = CDF::Mail.new(user.mail_temporary_path) - m.from, m.content_type = user.friendlly_local_email, get_mail_prefs.mail_type - m - end - - - def send_part(part) - if part.content_type == "text/html" - disposition = "inline" - elsif part.content_type.include?("image/") - disposition = "inline" - else - disposition = "attachment" - end - headers['Content-Length'] = part.body.size - response.headers['Accept-Ranges'] = 'bytes' - headers['Content-type'] = part.content_type.strip - headers['Content-Disposition'] = disposition << %(; filename="#{part.header['content-type']['name']}") - render :text => part.body - end - - def get_parts(mail) - parts = Array.new - parts << mail - mail.parts.each { |part| - if part.multipart? - parts = parts.concat(get_parts(part)) - elsif part.content_type and part.content_type.include?("rfc822") - parts = parts.concat(get_parts(TMail::Mail.parse(part.body))) << part - else - parts << part - end - } - parts - end - - def obtain_cookies_for_search_and_nav - @srch_class = ((cookies['_wmlms'] and cookies['_wmlms'] == 'closed') ? 'closed' : 'open') - @srch_img_src = ((cookies['_wmlms'] and cookies['_wmlms'] == 'closed') ? 'closed' : 'opened') - @ops_class = ((cookies['_wmlmo'] and cookies['_wmlmo'] == 'closed') ? 'closed' : 'open') - @ops_img_src = ((cookies['_wmlmo'] and cookies['_wmlmo'] == 'closed') ? 'closed' : 'opened') - end - - ################################################################### - ### Some fixed parameters and session variables - ################################################################### - def folder - @folders[@folder_name] - end - - def msg_id_param - params["msg_id"] - end - - def messages_param - params["messages"] - end - - def dst_folder - params["cpdest"] - end - - def operation_param - params["op"] - end -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb old mode 100755 new mode 100644 index 108ec48..de6be79 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,138 +1,2 @@ -# The methods added to this helper will be available to all templates in the application. module ApplicationHelper - include NavigationHelper - - protected - - def format_datetime(datetime) - datetime.strftime "%d.%m.%Y %H:%M" - end - - def errors_base(form_name) - errors = instance_variable_get("@#{form_name}").errors.on_base() - errors_out = "" - if errors - errors = [errors] unless errors.is_a? Array - errors.each do |e| - errors_out << "#{e}" - end - end - errors_out - end - - # Useful abstraction for form input fields - combines an input field with error message (if any) - # and writes an appropriate style (for errors) - # Usage: - # form_input :text_field, 'postform', 'subject' - # form_input :text_area, 'postform', 'text', 'Please enter text:', 'cols' => 80 - # form_input :hidden_field, 'postform', 'topic_id' - def form_input(helper_method, form_name, field_name, prompt = field_name.capitalize, options = {}) - case helper_method.to_s - when 'hidden_field' - self.hidden_field(form_name, field_name) - when /^.*button$/ - <<-EOL - - #{self.send(helper_method, form_name, prompt, options)} - - EOL - else - field = ( - if :select == helper_method - self.send(helper_method, form_name, field_name, options.delete('values'), options) - elsif :collection_select == helper_method - self.send(helper_method, form_name, field_name, options.delete('collection'), options.delete('value_method'), options.delete('text_method'), options) - else - self.send(helper_method, form_name, field_name, options) - end) - errors = instance_variable_get("@#{form_name}").errors[field_name] unless instance_variable_get("@#{form_name}").nil? - errors = Array.new if errors.nil? - errors_out = "" - if errors - errors = [errors] unless errors.is_a? Array - errors.each do |e| - errors_out << "#{e}" - end - end - if options['class'] == 'two_columns' - <<-EOL - - - #{field}#{errors_out} - - EOL - else - <<-EOL - #{prompt}: - #{field}#{errors_out} - EOL - end - end - end - - # Helper method that has the same signature as real input field helpers, but simply displays - # the value of a given field enclosed within

tags. - # Usage: - # <%= form_input :read_only_field, 'new_user', 'name', _('user_name')) %> - def read_only_field(form_name, field_name, html_options) - "#{instance_variable_get('@' + form_name)[field_name]}" - end - - def submit_button(form_name, prompt, html_options) - %{} - end - - # Converts a hash to XML attributes string. E.g.: - # to_attributes('a' => 'aaa', 'b' => 1) - # => 'a="aaa" b="1" ' - def attributes(hash) - hash.keys.inject("") { |attrs, key| attrs + %{#{key}="#{hash[key]}" } } - end - - def initListClass - @itClass = 1 - end - - def popListClass - ret = getListClass - @itClass = @itClass + 1 - return ret - end - - def getListClass - return "even" if @itClass%2 == 0 - return "odd" if @itClass%2 == 1 - end - - def get_meta_info - '' - '' - '' - '' - '' - end - - def user - @user = Customer.find(@session["user"]) if @user.nil? - @user - end - - def link_main - link_to( t(:contacts), contacts_path) - end - - def alternator - if @alternator.nil? - @alternator = 1 - end - - @alternator = -@alternator - - if @alternator == -1 - return "even" - else - return "odd" - end - end - end diff --git a/app/helpers/contact_group_helper.rb b/app/helpers/contact_group_helper.rb deleted file mode 100755 index a4f14e8..0000000 --- a/app/helpers/contact_group_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ContactGroupHelper - def link_save() "/contact_group/save" end - def link_list() "/contact_group/list" end -end diff --git a/app/helpers/contacts_helper.rb b/app/helpers/contacts_helper.rb deleted file mode 100755 index 6eb7d9a..0000000 --- a/app/helpers/contacts_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -module ContactsHelper - -end diff --git a/app/helpers/core_helper.rb b/app/helpers/core_helper.rb new file mode 100644 index 0000000..1e2635d --- /dev/null +++ b/app/helpers/core_helper.rb @@ -0,0 +1,2 @@ +module CoreHelper +end diff --git a/app/helpers/folders_helper.rb b/app/helpers/folders_helper.rb deleted file mode 100755 index d27e7b4..0000000 --- a/app/helpers/folders_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module FoldersHelper -end diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb deleted file mode 100755 index 59ca17b..0000000 --- a/app/helpers/navigation_helper.rb +++ /dev/null @@ -1,60 +0,0 @@ -module NavigationHelper - def link_back_to_messages - link_to("«" << t(:back_to_message), :controller=>"webmail", :action=>"messages") - end - - def link_send_mail - link_to( t(:compose), :controller=>"webmail", :action=>"compose") - end - - def link_mail_prefs - link_to( t(:preferences), :controller=>"webmail", :action=>"prefs") - end - - def link_mail_filters - link_to( t(:filters), :controller=>"webmail", :action=>"filters") - end - - def folder_manage_link(folder) - if folder.name == CDF::CONFIG[:mail_trash] or folder.name == CDF::CONFIG[:mail_inbox] or folder.name == CDF::CONFIG[:mail_sent] - short_fn(folder) - else - short_fn(folder) + ' ' + link_to(t(:delete), folder_path(folder.name), :method => :delete) - end - end - - def link_import_preview() "/contacts/import_preview" end - def link_main_index() root_url end - def link_contact_import() url_for(:controller => :contacts, :action => :import) end - def link_contact_choose() url_for(:controller => :contacts, :action => :choose) end - - def link_contact_list - link_to(t(:list), :controller => :contacts, :action => :index) - end - - def link_contact_add_one - link_to(t(:add_one_contact), new_contact_path) - end - - def link_contact_add_multiple - link_to(t(:add_multiple), :controller => :contacts, :action => "add_multiple") - end - - def link_contact_group_list - link_to(t(:groups), :controller => :contact_group, :action => :index) - end - - def link_folders - link_to( t(:folders), :controller=>:webmail, :action=>:messages) - end - - private - - def short_fn(folder) - if folder.name.include? folder.delim - folder.name.split(folder.delim).last - else - folder.name - end - end -end diff --git a/app/helpers/webmail_helper.rb b/app/helpers/webmail_helper.rb deleted file mode 100755 index 4204fbe..0000000 --- a/app/helpers/webmail_helper.rb +++ /dev/null @@ -1,167 +0,0 @@ -require 'cdfutils' -require 'mail2screen' - -module WebmailHelper - include Mail2Screen - def link_compose_new - link_to(t(:compose_txt), :controller=>"webmail", :action=>"compose") - end - - def link_refresh - link_to(t(:refresh), :controller=>"webmail", :action=>"refresh") - end - - def link_message_list - link_to(_('Message list'), :controller=>"webmail", :action=>"messages") - end - - def link_reply_to_sender(msg_id) - link_to(t(:reply), :controller=>"webmail", :action=>"reply", :params=>{"msg_id"=>msg_id}) - end - - def link_forward_message(msg_id) - link_to(t(:forward), :controller=>"webmail", :action=>"forward", :params=>{"msg_id"=>msg_id}) - end - - def link_flag_for_deletion(msg_id) - link_to(t(:delete), :controller=>"webmail", :action=>"delete", :params=>{"msg_id"=>msg_id}) - end - - def link_view_source(msg_id) - link_to(t(:view_source), {:controller=>"webmail", :action=>"view_source", :params=>{"msg_id"=>msg_id}}, {'target'=>"_blank"}) - end - - def link_filter_add - link_to(t(:add_filter), :controller=>'webmail', :action=>'filter_add') - end - - def folder_link(folder) - return folder.name if folder.attribs.include?(:Noselect) - folder_name = short_fn(folder) - folder_name = t(folder_name.downcase.to_sym, :default => folder_name) - title = folder.unseen > 0 ? "#{folder_name} (#{folder.unseen})" : "#{folder_name}" - link = link_to title, :controller => 'webmail', :action => 'messages', :folder_name => folder.name - link = content_tag('b', link) if folder.name == @folder_name - link += raw(' ' + empty_trash_link(folder.name)) if folder.trash? - logger.info link - link - end - - def message_date(datestr) - t = Time.now - begin - if datestr.kind_of?(String) - d = (Time.rfc2822(datestr) rescue Time.parse(value)).localtime - else - d = datestr - end - if d.day == t.day and d.month == t.month and d.year == t.year - d.strftime("%H:%M") - else - d.strftime("%Y-%m-%d") - end - rescue - begin - d = imap2time(datestr) - if d.day == t.day and d.month == t.month and d.year == t.year - d.strftime("%H:%M") - else - d.strftime("%Y-%m-%d") - end - rescue - datestr - end - end - end - - def attachment(att, index) - ret = "#{att.filename}" - # todo: add link to delete attachment - #ret << - ret << "" - ret << "" - ret << "" - end - - def link_filter_up(filter_id) - link_to(_('Up'), :controller=>"webmail", :action=>"filter_up", :id=>filter_id) - end - - def link_filter_down(filter_id) - link_to(_('Down'), :controller=>"webmail", :action=>"filter_down", :id=>filter_id) - end - - def link_filter_edit(filter_id) - link_to(_('Edit'), :controller=>"webmail", :action=>"filter", :id=>filter_id) - end - - def link_filter_delete(filter_id) - link_to(_('Delete'), :controller=>"webmail", :action=>"filter_delete", :id=>filter_id) - end - - def page_navigation_webmail(pages) - nav = "

" - - nav << "(#{pages.length} #{t :pages})   " - - window_pages = pages.current.window.pages - nav << "..." unless window_pages[0].first? - for page in window_pages - if pages.current == page - nav << page.number.to_s << " " - else - nav << link_to(page.number, :controller=>"webmail", :action=>'messages', :page=>page.number) << " " - end - end - nav << "..." unless window_pages[-1].last? - nav << "   " - - nav << link_to(t(:first), :controller=>"webmail", :action=>'messages', :page=>@pages.first.number) << " | " unless @pages.current.first? - nav << link_to(t(:prev), :controller=>"webmail", :action=>'messages', :page=>@pages.current.previous.number) << " | " if @pages.current.previous - nav << link_to(t(:next), :controller=>"webmail", :action=>'messages', :page=>@pages.current.next.number) << " | " if @pages.current.next - nav << link_to(t(:last), :controller=>"webmail", :action=>'messages', :page=>@pages.last.number) << " | " unless @pages.current.last? - - nav << "

" - - return nav - end - - def parse_subject(subject) - begin - if mime_encoded?(subject) - if mime_decode(subject) == '' - _('(No subject)') - else - mime_decode(subject) - end - else - if from_qp(subject) == '' - _('(No subject)') - else - from_qp(subject) - end - end - rescue Exception => ex - RAILS_DEFAULT_LOGGER.debug('Exception occured - #{ex}') - return "" - end - end - - def message_size(size) - if size / (1024*1024) > 0 - return "#{(size / (1024*1024)).round} MB" - elsif size / 1024 > 0 - return "#{(size / (1024)).round} KB" - else - return "#{size} B" - end - end - - private - - def empty_trash_link(folder_name) - link_to( "(#{t :empty})", - { :controller => "webmail", :action => "empty", :params=>{"folder_name"=>folder_name}}, - :confirm => t(:want_to_empty_trash_message)) - end -end diff --git a/app/models/contact.rb b/app/models/contact.rb deleted file mode 100755 index 97ffac8..0000000 --- a/app/models/contact.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'cdfutils' -require_association 'contact_group' - -class Contact < ActiveRecord::Base - - validate :check_fname_and_lname, :check_mail_cannot_be_changed - validate :check_email, :on => :create - - has_and_belongs_to_many :groups, :class_name => "ContactGroup", :join_table => "contact_contact_groups", :association_foreign_key => "contact_group_id", :foreign_key => "contact_id" - - # Finder methods follow - def Contact.find_by_user(user_id) - find(:all, :conditions => ["customer_id = ?", user_id], :order => "fname asc", :limit => 10) - end - - def Contact.find_by_user_email(user_id, email) - find(:first, :conditions => ["customer_id = #{user_id} and email = ?", email]) - end - - def Contact.find_by_group_user(user_id, grp_id) - result = Array.new - find(:all, :conditions => ["customer_id = ?", user_id], :order => "fname asc").each { |c| - begin - c.groups.find(grp_id) - result << c - rescue ActiveRecord::RecordNotFound - end - } - result - end - - scope :for_customer, lambda{ |customer_id| {:conditions => {:customer_id => customer_id}} } - scope :letter, lambda{ |letter| {:conditions => ["contacts.fname LIKE ?", "#{letter}%"]} } - - def Contact.find_by_user_letter(user_id, letter) - find_by_sql("select * from contacts where customer_id=#{user_id} and substr(UPPER(fname),1,1) = '#{letter}' order by fname") - end - - def full_name - "#{fname} #{lname}" - end - - def show_name - "#{fname} #{lname}" - end - - def full_address - "#{fname} #{lname}<#{email}>" - end - - protected - def check_fname_and_lname - errors.add 'fname', t(:validate_fname_error) unless self.fname =~ /^.{2,20}$/i - errors.add 'lname', t(:validate_lname_error) unless self.lname =~ /^.{2,20}$/i - end - - def check_mail_cannot_be_changed - # Contact e-mail cannot be changed - unless self.new_record? - old_record = Contact.find(self.id) - errors.add 'email', t(:contact_cannot_be_changed) unless old_record.email == self.email - end - end - - def check_mail - # Contact e-mail cannot be changed, so we only need to validate it on create - errors.add 'email', t(:validate_email_error) unless valid_email?(self.email) - # Already existing e-mail in contacts for this user is not allowed - if self.new_record? - if Contact.find :first, :conditions => {:email => email, :customer_id => customer_id} - errors.add('email', I18n.t(:email_exists)) - end - end - end - -end diff --git a/app/models/contact_group.rb b/app/models/contact_group.rb deleted file mode 100755 index 63da4c1..0000000 --- a/app/models/contact_group.rb +++ /dev/null @@ -1,30 +0,0 @@ -class ContactGroup < ActiveRecord::Base - has_and_belongs_to_many :contacts, :class_name => "Contact", :join_table => "contact_contact_groups", :association_foreign_key => "contact_id", :foreign_key => "contact_group_id" - - validate :check_group_name - validate :check_group_id, :on => :create - validate :check_group_uniqness, :on => :update - - def ContactGroup.find_by_user(user_id) - find_by_sql("select * from contact_groups where customer_id = #{user_id} order by name asc") - end - - protected - - def check_group_name - errors.add('name', t(:contactgroup_name_invalid)) unless self.name =~ /^.{1,50}$/i - end - - def check_group_id - if ContactGroup.find_first(["name = '#{name}' and customer_id = #{user_id}"]) - errors.add("name", _('Please enter group name (1 to 50 characters)')) - end - end - - def check_group_uniqness - if ContactGroup.find_first(["name = '#{name}' and customer_id = #{user_id} and id <> #{id}"]) - errors.add("name", _('You already have contact group with this name')) - end - end - -end diff --git a/app/models/customer.rb b/app/models/customer.rb deleted file mode 100755 index a9787a3..0000000 --- a/app/models/customer.rb +++ /dev/null @@ -1,32 +0,0 @@ -require_dependency 'maildropserializator' -class Customer < ActiveRecord::Base - include MaildropSerializator - - has_many :filters, :order => "order_num" - has_one :mail_pref - attr_accessor :password - - def mail_temporary_path - "#{CDF::CONFIG[:mail_temp_path]}/#{self.email}" - end - - def friendlly_local_email - encode_email("#{self.fname} #{self.lname}", check_for_domain(email)) - end - - def mail_filter_path - "#{CDF::CONFIG[:mail_filters_path]}/#{self.email}" - end - - def local_email - self.email - end - - def check_for_domain(email) - if email && !email.nil? && !email.include?("@") && CDF::CONFIG[:send_from_domain] - email + "@" + CDF::CONFIG[:send_from_domain] - else - email - end - end -end diff --git a/app/models/imap_message.rb b/app/models/imap_message.rb deleted file mode 100755 index 9231c07..0000000 --- a/app/models/imap_message.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'mail2screen' -class ImapMessage < ActiveRecord::Base - include Mail2Screen - - def set_folder(folder) - @folder = folder - end - - def full_body - @folder.mailbox.imap.uid_fetch(uid, "BODY[]").first.attr["BODY[]"] - end - - def from_addr=(fa) - self.from = fa.to_yaml - self.from_flat = short_address(fa) - end - - def from_addr - begin - YAML::load(from) - rescue Object - from - end - end - - def to_addr=(ta) - self.to = ta.to_yaml - self.to_flat = short_address(ta) - end - - def to_addr - begin - YAML::load(to) - rescue Object - to - end - end - - def self.getAll(userName,folderName,sortOrder='date desc') - self.all(:conditions => ["username = ? and folder_name = ?", userName, folderName],:order => sortOrder) - end - -end diff --git a/app/models/mail_pref.rb b/app/models/mail_pref.rb deleted file mode 100755 index 4eeca28..0000000 --- a/app/models/mail_pref.rb +++ /dev/null @@ -1,9 +0,0 @@ -# require_association 'customer' - -class MailPref < ActiveRecord::Base - belongs_to :customer - -# def MailPref.find_by_customer(customer_id) -# find :first, :conditions => (["customer_id = #{customer_id}"]) -# end -end diff --git a/app/views/contents_moved_to_original_theme_directory b/app/views/contents_moved_to_olive_theme_directory old mode 100644 new mode 100755 similarity index 100% rename from app/views/contents_moved_to_original_theme_directory rename to app/views/contents_moved_to_olive_theme_directory diff --git a/arts/logo2.xcf b/arts/logo2.xcf old mode 100644 new mode 100755 diff --git a/arts/logo3.xcf b/arts/logo3.xcf old mode 100644 new mode 100755 diff --git a/config.ru b/config.ru old mode 100755 new mode 100644 diff --git a/config/application.rb b/config/application.rb old mode 100755 new mode 100644 index 16387c7..2f9048c --- a/config/application.rb +++ b/config/application.rb @@ -15,9 +15,6 @@ module Mailr # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) - config.autoload_paths << Rails.root.join("vendor/ezcrypto-0.1.1/lib") - config.autoload_paths << Rails.root.join("lib/webmail") - # 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 ] @@ -30,8 +27,8 @@ module Mailr # 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 = :en + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de # JavaScript files you want as :defaults (application.js is always included). # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) @@ -41,23 +38,5 @@ module Mailr # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] - - default_config_path = 'config/default_site' - default_config = Rails.root.join(default_config_path) - require default_config - begin - require Rails.root.join("config/site") - CDF::CONFIG.update(CDF::LOCALCONFIG) if CDF::LOCALCONFIG - rescue LoadError - STDERR.puts 'WARNING: config/site.rb not found, using default settings from ' + default_config_path - end - - #if CONFIG[:locale] is nil then I18n.default_locale will be used - config.i18n.default_locale = CDF::CONFIG[:locale] - - require 'tmail_patch' - $KCODE = 'u' - require 'jcode' - end end diff --git a/config/boot.rb b/config/boot.rb old mode 100755 new mode 100644 diff --git a/config/database.yml.example b/config/database.yml.example old mode 100644 new mode 100755 diff --git a/config/default_site.rb b/config/default_site.rb deleted file mode 100755 index c76969d..0000000 --- a/config/default_site.rb +++ /dev/null @@ -1,52 +0,0 @@ -# Site-specific parameters -# These are Mailr constants. If you want to overwrite -# some of them - create file config/site.rb -# containing new constants in LOCALCONFIG module variable - they -# will overwrite default values. Example site.rb: -# -# module CDF -# LOCALCONFIG = { -# :mysql_version => '4.1', -# :default_encoding => 'utf-8', -# :imap_server => 'your.imap.server', -# :imap_auth => 'LOGIN' -# } -# end - -module CDF - CONFIG = { - :mysql_version => '4.0', - :default_language => 'en', - :default_encoding => 'ISO-8859-1', - :mail_charset => 'ISO-8859-1', - :mail_inbox => 'INBOX', - :mail_trash => 'INBOX.Trash', - :mail_sent => 'INBOX.Sent', - :mail_bulk_sent => "INBOX.SentBulk", - :mail_spam => "INBOX.Spam", - :mail_temp_path => 'mail_temp', - :mail_filters_path => '/home/vmail/mailfilters', - :mail_send_types => {"Plain text" => "text/plain", "HTML"=>"text/html", "HTML and PlainText" => "multipart"}, - :mail_message_rows => [5, 10, 15, 20, 25, 30, 50], - :mail_filters_fields => {'From' => '^From', 'To' => '^To', 'CC' => '^CC', 'Subject' => '^Subject', 'Body' => '^Body'}, - :mail_filters_expressions => ['contains', 'starts with'], - :mail_search_fields => ['FROM', 'TO', 'CC', 'SUBJECT', 'BODY'], - :temp_file_location => ".", - :contacts_per_page => 15, - :contact_letters => ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'], - :upload_file_temp_path => "/tmp", - :imap_server => 'localhost', - :imap_use_ssl => false, - :imap_port => 143, - :imap_auth => 'PLAIN', # 'LOGIN', 'NOAUTH' - :encryption_salt => 'EnCr1p10n$@lt', - :encryption_password => '$0MeEncr1pt10nP@a$sw0rd', - :debug_imap => false, - :crypt_session_pass => true, # Set it to false (in site.rb) if you get any error messages like - # "Unsupported cipher algorithm (aes-128-cbc)." - meaning that OpenSSL modules for crypt algo is not loaded. Setting it to false will store password in session in plain format! - :send_from_domain => nil, # Set this variable to your domain name in site.rb if you make login to imap only with username (without '@domain') - :imap_bye_timeout_retry_seconds => 2, - :default_theme => 'original' - } -end - diff --git a/config/defaults.yml b/config/defaults.yml new file mode 100644 index 0000000..99b94e0 --- /dev/null +++ b/config/defaults.yml @@ -0,0 +1,2 @@ +theme: olive +locale: en diff --git a/config/environment.rb b/config/environment.rb old mode 100755 new mode 100644 diff --git a/config/environments/development.rb b/config/environments/development.rb old mode 100755 new mode 100644 diff --git a/config/environments/production.rb b/config/environments/production.rb old mode 100755 new mode 100644 diff --git a/config/environments/test.rb b/config/environments/test.rb old mode 100755 new mode 100644 diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb old mode 100755 new mode 100644 diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb old mode 100755 new mode 100644 diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb old mode 100755 new mode 100644 diff --git a/config/initializers/pluralization.rb b/config/initializers/pluralization.rb deleted file mode 100755 index a119797..0000000 --- a/config/initializers/pluralization.rb +++ /dev/null @@ -1,36 +0,0 @@ -# config/initializers/pluralization.rb -module I18n::Backend::Pluralization - # rules taken from : http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html - def pluralize(locale, entry, n) - return entry unless entry.is_a?(Hash) && n - if n == 0 && entry.has_key?(:zero) - key = :zero - else - key = case locale - when :pl # Polish - n==1 ? :one : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? :few : :other - when :cs, :sk # Czech, Slovak - n==1 ? :one : (n>=2 && n<=4) ? :few : :other - when lt # Lithuanian - n%10==1 && n%100!=11 ? :one : n%10>=2 && (n%100<10 || n%100>=20) ? :few : :other - when :lv # Latvian - n%10==1 && n%100!=11 ? :one : n != 0 ? :few : :other - when :ru, :uk, :sr, :hr # Russian, Ukrainian, Serbian, Croatian - n%10==1 && n%100!=11 ? :one : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? :few : :other - when :sl # Slovenian - n%100==1 ? :one : n%100==2 ? :few : n%100==3 || n%100==4 ? :many : :other - when :ro # Romanian - n==1 ? :one : (n==0 || (n%100 > 0 && n%100 < 20)) ? :few : :other - when :gd # Gaeilge - n==1 ? :one : n==2 ? :two : :other; - # add another language if you like... - else - n==1 ? :one : :other # default :en - end - end - raise InvalidPluralizationData.new(entry, n) unless entry.has_key?(key) - entry[key] - end -end - -I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization) diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb old mode 100755 new mode 100644 index d168e04..dbec6e1 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -4,4 +4,4 @@ # 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. -Mailr::Application.config.secret_token = 'ade84d567b0c637fd3547fd18b97d1677fd6ca3c5331e6ed1a1b13bb6a7823cc367cbe317caf102f29f8c35eb487ff3ca33e6321d037c14ebb055eb530841ff6' +Mailr::Application.config.secret_token = 'f07b5830035b1471d3c008debde5c152077eaff97f0dfcebaf265fe96db24dc5af46eb27e149e3077df89d7dbe2eb088ab7ef7b0e8b496d7ca005e31f6dc3017' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb old mode 100755 new mode 100644 index b5dbc5b..89479f4 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -5,4 +5,4 @@ Mailr::Application.config.session_store :cookie_store, :key => '_mailr_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") -# Rails3::Application.config.session_store :active_record_store +# Mailr::Application.config.session_store :active_record_store diff --git a/config/locales/en.yml b/config/locales/en.yml index 0411e8b..7110637 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -65,3 +65,6 @@ en: total_messages: Total messages unseen: Unseen please_login: Log in + site_link: https://github.com/lmanolov/mailr + user_logged_out: User was logged out + diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 857b72d..145ae75 100755 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -14,7 +14,7 @@ pl: filters: Filtry contacts: Kontakty search: Szukaj - search_txt: Szukaj w polu wiadomoÅ›ci + search_txt: ciÄ…g znaków refresh: OdÅ›wież operations: Akcje operations_txt: Akcje na zaznaczonych wiadomoÅ›ciach @@ -70,3 +70,8 @@ pl: add_to_contacts: Dodaj do kontaktów want_to_empty_trash_message: Czy chcesz opróznic kosz? site_link: https://github.com/lmanolov/mailr + marked_messages: zaznaczone wiadomoÅ›ci + to_folder: do folderu + message_field: Pole wiadomoÅ›ci + no_messages_found: Nie znaleziono żadnych wiadomoÅ›ci + diff --git a/config/routes.rb b/config/routes.rb old mode 100755 new mode 100644 index 80ae421..aa8dc28 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,26 +1,11 @@ Mailr::Application.routes.draw do - themes_for_rails - resources :folders - resources :contacts do - collection do - get :add_multiple - end - member do - get :add_from_mail - end - end + themes_for_rails - resources :contact_groups - match '/' => 'webmail#index' - match 'webmail' => 'webmail#index' - match 'webmail/:action' => 'webmail#index' - match '/contact/:action' => 'contacts#index' - match 'admin/main' => 'login#logout' - match ':controller/service.wsdl' => '#wsdl' - match '/:controller(/:action(/:id))' + get "core/login" + get "core/logout" + post "core/authenticate" -end # The priority is based upon order of creation: # first created -> highest priority. @@ -78,3 +63,4 @@ end # This is a legacy wild controller route that's not recommended for RESTful applications. # Note: This route will make all actions in every controller accessible via GET requests. # match ':controller(/:action(/:id(.:format)))' +end diff --git a/db/migrate/20090107193228_init.rb b/db/migrate/20090107193228_init.rb deleted file mode 100755 index 24d739c..0000000 --- a/db/migrate/20090107193228_init.rb +++ /dev/null @@ -1,66 +0,0 @@ -class Init < ActiveRecord::Migration - def self.up - create_table :customers do |t| - t.string :fname, :lname, :email - t.integer :customer_id - t.timestamps - end - - create_table :filters do |t| - t.string :name, :destination_folder - t.integer :customer_id, :order_num - t.timestamps - end - - create_table :expressions do |t| - t.string :field_name, :operator, :expr_value - t.integer :filter_id - t.boolean :case_sensitive - t.timestamps - end - - create_table :mail_prefs do |t| - t.string :mail_type - t.integer :wm_rows, :default => 20 - t.integer :customer_id - t.boolean :check_external_mail - t.timestamps - end - - create_table :contacts do |t| - t.string :fname, :lname, :email, :hphone, :wphone, :mobile, :fax - t.text :notes - t.integer :customer_id - t.timestamps - end - - create_table :contact_groups do |t| - t.string :name - t.integer :customer_id - t.timestamps - end - - create_table :contact_contact_groups do |t| - t.integer :contact_id, :contact_group_id - t.timestamps - end - - create_table :imap_messages do |t| - t.string :folder_name, :username, :msg_id, :from, :from_flat, :to, :to_flat, :subject, :content_type - t.integer :uid, :size - t.boolean :unread - t.datetime :date - end - end - - def self.down - drop_table :imap_messages - drop_table :contact_contact_groups - drop_table :contact_groups - drop_table :contacts - drop_table :mail_prefs - drop_table :expressions - drop_table :filters - drop_table :customers - end -end diff --git a/db/schema.rb b/db/schema.rb deleted file mode 100755 index 16e407a..0000000 --- a/db/schema.rb +++ /dev/null @@ -1,96 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20090107193228) do - - create_table "contact_contact_groups", :force => true do |t| - t.integer "contact_id" - t.integer "contact_group_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "contact_groups", :force => true do |t| - t.string "name" - t.integer "customer_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "contacts", :force => true do |t| - t.string "fname" - t.string "lname" - t.string "email" - t.string "hphone" - t.string "wphone" - t.string "mobile" - t.string "fax" - t.text "notes" - t.integer "customer_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "customers", :force => true do |t| - t.string "fname" - t.string "lname" - t.string "email" - t.integer "customer_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "expressions", :force => true do |t| - t.string "field_name" - t.string "operator" - t.string "expr_value" - t.integer "filter_id" - t.boolean "case_sensitive" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "filters", :force => true do |t| - t.string "name" - t.string "destination_folder" - t.integer "customer_id" - t.integer "order_num" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "imap_messages", :force => true do |t| - t.string "folder_name" - t.string "username" - t.string "msg_id" - t.string "from" - t.string "from_flat" - t.string "to" - t.string "to_flat" - t.string "subject" - t.string "content_type" - t.integer "uid" - t.integer "size" - t.boolean "unread" - t.datetime "date" - end - - create_table "mail_prefs", :force => true do |t| - t.string "mail_type" - t.integer "wm_rows", :default => 20 - t.integer "customer_id" - t.boolean "check_external_mail" - t.datetime "created_at" - t.datetime "updated_at" - end - -end diff --git a/db/seeds.rb b/db/seeds.rb old mode 100755 new mode 100644 diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP old mode 100755 new mode 100644 diff --git a/lib/cdfutils.rb b/lib/cdfutils.rb deleted file mode 100755 index e780a01..0000000 --- a/lib/cdfutils.rb +++ /dev/null @@ -1,183 +0,0 @@ -MIME_ENCODED = /=\?([a-z\-0-9]*)\?[QB]\?([a-zA-Z0-9+\/=\_\-]+)\?=/i -IMAP_EMAIL_ENVELOPE_FORMAT = /([a-zA-Z\-\.\_]*@[a-zA-Z\-\.\_]*)/ -IMAP_EMAIL_ENVELOPE_FORMAT2 = /(.*)<([a-zA-Z\-\.\_]*@[a-zA-Z\-\.\_]*)>/ - -require 'iconv' - -def valid_email?(email) - email.size < 100 && email =~ /.@.+\../ && email.count('@') == 1 -end - -def mime_encoded?( str ) - return false if str.nil? - not (MIME_ENCODED =~ str).nil? -end - -def from_qp(str, remove_underscore = true) - return '' if str.nil? - result = str.gsub(/=\r\n/, "") - result = result.gsub(/_/, " ") if remove_underscore - result.gsub!(/\r\n/m, $/) - result.gsub!(/=([\da-fA-F]{2})/) { $1.hex.chr } - result -end - -def mime_decode(str, remove_underscore = true) - return '' if str.nil? - str.gsub(MIME_ENCODED) {|s| - enc = s.scan(MIME_ENCODED).flatten - if /\?Q\?/i =~ s - begin - Iconv.conv("UTF-8", enc[0], from_qp(enc[1], remove_underscore)) - rescue - from_qp(enc[1], remove_underscore) - end - else - begin - Iconv.conv("UTF-8", enc[0], enc[1].unpack("m*").to_s) - rescue - enc[1].unpack("m*").to_s - end - end - } -end - -def imap2friendlly_email(str) - begin - if str === IMAP_EMAIL_ENVELOPE_FORMAT - email = str.scan(IMAP_EMAIL_ENVELOPE_FORMAT)[0][0] - else - email = str.scan(IMAP_EMAIL_ENVELOPE_FORMAT2)[0][0] - end - name = str.slice(0, str.rindex(email)-1) - name = decode(name).to_s if mime_encoded?(name) - return "#{name.nil? ? '' : name.strip}<#{email}>" - rescue - "Error parsing str - #{str.scan(IMAP_EMAIL_ENVELOPE_FORMAT)} - #{str.scan(IMAP_EMAIL_ENVELOPE_FORMAT2)}" - end -end - -def imap2friendlly_name(str) - begin - email = str.scan(IMAP_EMAIL_ENVELOPE_FORMAT)[0][0] - name = str.slice(0, str.rindex(email)) - if name.nil? or name.strip == "" - return email - else - return name - end - rescue - str - end -end - -def imap2friendlly_full_name(str) - begin - email = str.scan(IMAP_EMAIL_ENVELOPE_FORMAT)[0][0] - name = str.slice(0, str.rindex(email)) - if name.nil? or name.strip == "" - return email - else - return "#{name}<#{email}>" - end - rescue - str - end -end - -def imap2name_only(str) - email = str.scan(IMAP_EMAIL_ENVELOPE_FORMAT)[0][0] - name = str.slice(0, str.rindex(email)) - return "#{name.nil? ? '' : name.strip}" -end - -def imap2time(str) - begin - vals = str.scan(/(...), (.?.) (...) (....) (..):(..):(..) (.*)/)[0] - Time.local(vals[3],vals[2],vals[1],vals[4],vals[5],vals[6]) - rescue - Time.now - end -end - -def encode_email(names, email) - nameen = "" - names.each_byte { | ch | nameen = nameen +"=" + sprintf("%X",ch) } - return "=?#{CDF::CONFIG[:mail_charset]}?Q?#{nameen}?= <#{email}>" -end - -# ############################# -# HTML utils -# ############################# -def replace_tag(tag, attrs) - replacements = {"body" => "", - "/body" => "", - "meta" => "", - "/meta" => "", - "head" => "", - "/head" => "", - "html" => "", - "/html" => "", - "title" => "
", - "/title" => "
", - "div" => "", - "/div" => "", - "span" => "", - "/span" => "", - "layer" => "", - "/layer" => "", - "br" => "
", - "/br" => "
", - "iframe" => "", - "/iframe" => "", - "link" => "", - "/link" => "", - "style" => "
", - "/style" => "
", - "script" => "
", - "/script" => "
" } - replacements.fetch(tag.downcase, ("<" << tag.downcase << replace_attr(attrs) << ">")) -end - -def replace_attr(attrs) - if attrs - attrs.downcase.gsub("onload", "onfilter"). - gsub("onclick", "onfilter"). - gsub("onkeypress", "onfilter"). - gsub("javascript", "_javascript"). - gsub("JavaScript", "_javascript") - else - "" - end -end - -def clear_html(text) - attribute_key = /[\w:_-]+/ - attribute_value = /(?:[A-Za-z0-9\-_#\%\.,\/\:]+|(?:'[^']*?'|"[^"]*?"))/ - attribute = /(?:#{attribute_key}(?:\s*=\s*#{attribute_value})?)/ - attributes = /(?:#{attribute}(?:\s+#{attribute})*)/ - tag_key = attribute_key - tag = %r{<([!/?\[]?(?:#{tag_key}|--))((?:\s+#{attributes})?\s*(?:[!/?\]]+|--)?)>} - text.gsub(tag, '').gsub(/\s+/, ' ').strip - CGI::escape(text) -end - -def strip_html(text) - attribute_key = /[\w:_-]+/ - attribute_value = /(?:[A-Za-z0-9\-_#\%\.,\/\:]+|(?:'[^']*?'|"[^"]*?"))/ - attribute = /(?:#{attribute_key}(?:\s*=\s*#{attribute_value})?)/ - attributes = /(?:#{attribute}(?:\s+#{attribute})*)/ - tag_key = attribute_key - tag = %r{<([!/?\[]?(?:#{tag_key}|--))((?:\s+#{attributes})?\s*(?:[!/?\]]+|--)?)>} - res = text.gsub(tag) { |match| - ret = "" - match.scan(tag) { |token| - ret << replace_tag(token[0], token[1]) - } - ret - } - # remove doctype tags - xattributes = /(?:#{attribute_value}(?:\s+#{attribute_value})*)/ - xtag = %r{} - res.gsub(xtag, '') -end diff --git a/lib/imap_utils.rb b/lib/imap_utils.rb deleted file mode 100755 index d38fb20..0000000 --- a/lib/imap_utils.rb +++ /dev/null @@ -1,69 +0,0 @@ -module ImapUtils - private - - def load_imap_session - return if ['error_connection'].include?(action_name) - get_imap_session - end - - def get_imap_session - begin - @mailbox = IMAPMailbox.new(self.logger) - uname = (get_mail_prefs.check_external_mail == 1 ? user.email : user.local_email) - upass = get_upass - @mailbox.connect(uname, upass) - load_folders - rescue Exception => ex -# logger.error("Exception on loggin webmail session - #{ex} - #{ex.backtrace.join("\t\n")}") -# render :action => "error_connection" - render :text => ex.inspect, :content_type => 'text/plain' - end - end - - def close_imap_session - return if @mailbox.nil? or not(@mailbox.connected) - @mailbox.disconnect - @mailbox = nil - end - - def get_mail_prefs - if not(@mailprefs) - if not(@mailprefs = MailPref.find_by_customer_id(logged_customer)) - @mailprefs = MailPref.create("customer_id"=>logged_customer) - end - end - @mailprefs - end - - def get_upass - if CDF::CONFIG[:crypt_session_pass] - EzCrypto::Key.decrypt_with_password(CDF::CONFIG[:encryption_password], CDF::CONFIG[:encryption_salt], session["wmp"]) - else - # retrun it plain - session["wmp"] - end - end - - def load_folders - if have_to_load_folders?() - if params["folder_name"] - @folder_name = params["folder_name"] - else - @folder_name = session["folder_name"] ? session["folder_name"] : CDF::CONFIG[:mail_inbox] - end - session["folder_name"] = @folder_name - @folders = @mailbox.folders if @folders.nil? - end - end - - def user - @user = Customer.find(logged_customer) if @user.nil? - @user - end - - def have_to_load_folders? - return true if ['messages', 'delete', 'reply', 'forward', 'empty', 'message', 'download', - 'filter', 'filter_add', 'view_source', 'compose', 'prefs', 'filters'].include?(action_name) - return false - end -end diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep old mode 100755 new mode 100644 diff --git a/lib/tmail_patch.rb b/lib/tmail_patch.rb deleted file mode 100755 index bb86dd7..0000000 --- a/lib/tmail_patch.rb +++ /dev/null @@ -1,38 +0,0 @@ -module TMail - class Mail - def parse_header( f ) - name = field = nil - unixfrom = nil - - while line = f.gets - case line - when /\A[ \t]/ # continue from prev line - raise SyntaxError, 'mail is began by space' unless field - field << ' ' << line.strip - - when /\A([^\: \t]+):\s*/ # new header line - add_hf name, field if field - name = $1 - field = $' #.strip - - when /\A\-*\s*\z/ # end of header - add_hf name, field if field - name = field = nil - break - - when /\AFrom (\S+)/ - unixfrom = $1 - else - # treat as continue from previos - raise SyntaxError, 'mail is began by space' unless field - field << ' ' << line.strip - end - end - add_hf name, field if name - - if unixfrom - add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path'] - end - end - end -end diff --git a/lib/webmail/bounced_mail.rb b/lib/webmail/bounced_mail.rb deleted file mode 100755 index 3b30562..0000000 --- a/lib/webmail/bounced_mail.rb +++ /dev/null @@ -1,8 +0,0 @@ -class BouncedMail < ActiveRecord::Base - belongs_to :customer - belongs_to :contact - - def BouncedMail.find_by_customer_contact(cust_id, contact_id) - find_all(["customer_id = ? and contact_id = ?", cust_id, cotact_id], ["msg_date desc"]) - end -end diff --git a/lib/webmail/cdfmail.rb b/lib/webmail/cdfmail.rb deleted file mode 100755 index 28ad051..0000000 --- a/lib/webmail/cdfmail.rb +++ /dev/null @@ -1,306 +0,0 @@ -require 'tmail' -require 'net/smtp' -require 'mail_transform' - -module CDF -end - -class CDF::Mail - #include ActionMailer::Quoting #upgrade to Rails3 - - def initialize(senderTempLocation) - @attachments = Array.new - @sender_temp_location = senderTempLocation - @to_contacts = Array.new - end - - def customer_id() @customer_id end - - def customer_id=(arg) @customer_id = arg end - - def from() @from end - - def from=(arg) @from = arg end - - def to() @to end - - def to=(arg) @to = arg end - - def to_contacts() @to_contacts end - - def to_contacts=(arg) @to_contacts = arg end - - def toc=(arg) - @to_contacts = Array.new - arg.split(",").each { |token| @to_contacts << token.to_i unless token == "" or token.strip() == "undefined"} unless arg.nil? or arg == "undefined" - end - - def toc - ret = String.new - @to_contacts.each { |contact| - ret << "," unless ret == "" - if contact.kind_of?(Integer) - ret << contact.to_s unless contact.nil? or contact == 0 - else - ret << contact.id.to_s unless contact.nil? or contact.id.nil? - end - } - ret - end - - def bcc() @bcc end - - def bcc=(arg) @bcc = arg end - - def cc() @cc end - - def cc=(arg) @cc = arg end - - def subject() @subject end - - def subject=(arg) @subject = arg end - - def attachments - @attachments - end - - def add_attachment(attachment) - @attachments << attachment - end - - def multipart? - @attachments && @attachments.size > 0 - end - - def delete_attachment(att_filename) - @attachments.each { |att| att.delete_temp_data() if arr.filename == att_filename } - @attachments.delete_if() { |att| att.filename == att_filename } - end - - def delete_attachments() - @attachments.each { |att| att.delete_temp_data() } - @attachments = Array.new - end - - def body() @body end - - def body=(arg) @body = arg end - - def content_type() @content_type end - - def content_type=(arg) @content_type = arg end - - def temp_location() @sender_temp_location end - - def send_mail(db_msg_id = 0) - m = TMail::Mail.new - m.from, m.body = self.from, self.body - m.date = Time.now - m.subject, = quote_any_if_necessary("UTF-8", self.subject) - m.to = decode_addresses(self.to) - - m.cc, m.bcc = decode_addresses(self.cc), decode_addresses(self.bcc) - - if multipart? - m.set_content_type("multipart/mixed") - p = TMail::Mail.new(TMail::StringPort.new("")) - if @content_type.include?("text/plain") # here maybe we should encode in 7bit??!! - prepare_text(p, self.content_type, self.body) - elsif self.content_type.include?("text/html") - prepare_html(p, self.content_type, self.body) - elsif self.content_type.include?("multipart") - prepare_alternative(p, self.body) - end - m.parts << p - else - if @content_type.include?("text/plain") # here maybe we should encode in 7bit??!! - prepare_text(m, self.content_type, self.body) - elsif self.content_type.include?("text/html") - prepare_html(m, self.content_type, self.body) - elsif self.content_type.include?("multipart") - prepare_alternative(m, self.body) - end - end - # attachments - @attachments.each { |a| - m.parts << a.encoded - } - encmail = m.encoded - RAILS_DEFAULT_LOGGER.debug("Sending message \n #{encmail}") - Net::SMTP.start(ActionMailer::Base.smtp_settings[:address], ActionMailer::Base.smtp_settings[:port], - ActionMailer::Base.smtp_settings[:domain], ActionMailer::Base.smtp_settings[:user_name], - ActionMailer::Base.smtp_settings[:password], ActionMailer::Base.smtp_settings[:authentication]) do |smtp| - smtp.sendmail(encmail, m.from, m.destinations) - end - return encmail - end - - def forward(tmail, fb) - decoded_subject = mime_encoded?(tmail.subject) ? mime_decode(tmail.subject) : tmail.subject - self.subject = "[Fwd: #{decoded_subject}]" - attachment = CDF::Attachment.new(self) - attachment.body(tmail, fb) - end - - def reply(tmail, fb, type) - decoded_subject = mime_encoded?(tmail.subject) ? mime_decode(tmail.subject) : tmail.subject - self.subject = "Re: #{decoded_subject}" - tm = tmail.create_reply - self.to = tm.to - footer = "" - msg_id = "" - mt = MailTransform.new - self.body = mt.get_body(tmail, type) - end - - private - - def delimeter - if self.content_type == "text/plain" - "\n" - else - "
" - end - end - - def text2html(str) CGI.escapeHTML(str).gsub("\n", "
") end - - def html2text(txt) - clear_html(txt) - end - - def prepare_text(msg, ctype, bdy) - msg.set_content_type(ctype, nil, {"charset"=>"utf-8"}) - msg.transfer_encoding = "8bit" - msg.body = bdy - end - - def prepare_html(msg, ctype, bdy) - msg.set_content_type(ctype, nil, {"charset"=>"utf8"}) - msg.transfer_encoding = "8bit" - msg.body = bdy - end - - def prepare_alternative(msg, bdy) - bound = ::TMail.new_boundary - - msg.set_content_type("multipart/alternative", nil, {"charset"=>"utf8", "boundary"=>bound}) - msg.transfer_encoding = "8bit" - - ptext = TMail::Mail.new(TMail::StringPort.new("")) - phtml = TMail::Mail.new(TMail::StringPort.new("")) - - prepare_text(ptext, "text/plain", html2text(bdy)) - prepare_html(phtml, "text/html", bdy) - - msg.parts << ptext - msg.parts << phtml - end - - def decode_addresses(str) - ret = String.new - str.split(",").each { |addr| - if addr.slice(0,4) == "Grp+" - grp_id = addr.scan(/Grp\+([0-9]*):(.*)/)[0][0] - ContactGroup.find(:first, :conditions=>['customer_id = ? and id = ?', @customer_id, grp_id]).contacts.each { |contact| - ret << "," if not(ret == "") - @to_contacts << contact unless contact.nil? - ret << contact.full_address - ad, = quote_any_address_if_necessary(CDF::CONFIG[:mail_charset], contact.full_address) - ret << ad - } - else - ret << "," if not(ret == "") - ad, = quote_any_address_if_necessary(CDF::CONFIG[:mail_charset], addr) if not(addr.nil? or addr == "") - ret << ad if not(addr.nil? or addr == "") - end - } unless str.nil? or str.strip() == "" - ret - end -end - -class CDF::Attachment - - def initialize(arg) - @mail = arg - @mail.add_attachment(self) - @index = @mail.attachments.size - 1 - end - - def filename=(arg) - @filename = arg.tr('\\/:*?"\'<>|', '__________') - end - - def filename() @filename end - - def temp_filename=(arg) @temp_filename = arg end - - def temp_filename() @temp_filename end - - def content_type=(arg) @content_type = arg end - - def content_type() @content_type end - - def delete_temp_data() - File.delete(self.temp_filename) - end - - def file - File.open(self.temp_filename, "rb") { |fp| fp.read } - end - - def file=(data) - return if data.size == 0 - @content_type = data.content_type - self.filename = data.original_filename.scan(/[^\\]*$/).first - self.temp_filename = "#{@mail.temp_location}/#{@filename}" - check_store_path - data.rewind - File.open(@temp_filename, "wb") { |f| f.write(data.read) } - end - - def body(data, fb) - @content_type = "message/rfc822" - filename = data.content_type['filename'] - self.filename = filename.nil? ? (mime_encoded?(data.subject) ? mime_decode(data.subject) : data.subject) : filename - self.temp_filename = "#{@mail.temp_location}/#{@filename}" - check_store_path - File.open(@temp_filename, "wb") { |f| f.write(fb) } - end - - def check_store_path() - path = "" - "#{@mail.temp_location}".split(File::SEPARATOR).each { |p| - path << p - begin - Dir.mkdir(path) - rescue - end - path << File::SEPARATOR - } - end - - def encoded - p = TMail::Mail.new(TMail::StringPort.new("")) - data = self.file - p.body = data - if @content_type.include?("text/plain") # here maybe we should encode in 7bit??!! - p.set_content_type(@content_type, nil, {"charset"=>"utf-8"}) - p.transfer_encoding = "8bit" - elsif @content_type.include?("text/html") - p.set_content_type(@content_type, nil, {"charset"=>"utf8"}) - p.transfer_encoding = "8bit" - elsif @content_type.include?("rfc822") - p.set_content_type(@content_type, nil, {"charset"=>"utf8"}) - p.set_disposition("inline;") - p.transfer_encoding = "8bit" - else - p.set_content_type(@content_type, nil, {"name"=>@filename}) - p.set_disposition("inline; filename=#{@filename}") unless @filename.nil? - p.set_disposition("inline;") if @filename.nil? - p.transfer_encoding='Base64' - p.body = TMail::Base64.folding_encode(data) - end - return p - end -end diff --git a/lib/webmail/environment.rb b/lib/webmail/environment.rb deleted file mode 100755 index 46ce795..0000000 --- a/lib/webmail/environment.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'maildropserializator' -Customer.class_eval do - include MaildropSerializator - has_many :filters, :order => "order_num", :dependent => true -end \ No newline at end of file diff --git a/lib/webmail/expression.rb b/lib/webmail/expression.rb deleted file mode 100755 index 868588b..0000000 --- a/lib/webmail/expression.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Expression < ActiveRecord::Base -end diff --git a/lib/webmail/filter.rb b/lib/webmail/filter.rb deleted file mode 100755 index 22dd703..0000000 --- a/lib/webmail/filter.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Filter < ActiveRecord::Base - has_many :expressions -end diff --git a/lib/webmail/imap_message.rb b/lib/webmail/imap_message.rb deleted file mode 100755 index da0b989..0000000 --- a/lib/webmail/imap_message.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'mail2screen' -class ImapMessage < ActiveRecord::Base - include Mail2Screen - - def set_folder(folder) - @folder = folder - end - - def full_body - @folder.mailbox.imap.uid_fetch(uid, "BODY[]").first.attr["BODY[]"] - end - - def from_addr=(fa) - self.from = fa.to_yaml - self.from_flat = short_address(fa) - end - - def from_addr - begin - YAML::load(from) - rescue Object - from - end - end - - def to_addr=(ta) - self.to = ta.to_yaml - self.to_flat = short_address(ta) - end - - def to_addr - begin - YAML::load(to) - rescue Object - to - end - end -end \ No newline at end of file diff --git a/lib/webmail/imapmailbox.rb b/lib/webmail/imapmailbox.rb deleted file mode 100755 index 24d7345..0000000 --- a/lib/webmail/imapmailbox.rb +++ /dev/null @@ -1,526 +0,0 @@ -# Copyright (c) 2005, Benjamin Stiglitz -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Modifications (c) 2005 by littlegreen -# -require 'net/imap' - -Net::IMAP.debug = true if CDF::CONFIG[:debug_imap] - -class Net::IMAP - class PlainAuthenticator - def process(data) - return "\0#{@user}\0#{@password}" - end - - private - def initialize(user, password) - @user = user - @password = password - end - end - add_authenticator('PLAIN', PlainAuthenticator) - - class Address - def to_s - if(name) - "#{name} #{mailbox}@#{host}" - else - "#{mailbox}@#{host}" - end - end - end -end - -class AuthenticationError < RuntimeError -end - -class IMAPMailbox - attr_reader :connected - attr_accessor :selected_mailbox - cattr_accessor :logger - - def initialize(logger) - @selected_mailbox = '' - @folders = {} - @connected = false - @logger = logger - end - - def connect(username, password) - #logger.debug "*** connect: @connected" - unless @connected - use_ssl = CDF::CONFIG[:imap_use_ssl] ? true : false - port = CDF::CONFIG[:imap_port] || (use_ssl ? 993 : 143) - # logger.debug "*** IMAP params: use_ssl => #{use_ssl}, port => #{port}" - begin - @imap = Net::IMAP.new(CDF::CONFIG[:imap_server], port, use_ssl) - rescue Net::IMAP::ByeResponseError => bye - # make a timeout and retry - begin - System.sleep(CDF::CONFIG[:imap_bye_timeout_retry_seconds]) - @imap = Net::IMAP.new(CDF::CONFIG[:imap_server], port, use_ssl) - rescue Error => ex - # logger.error "Error on authentication!" - # logger.error bye.backtrace.join("\n") - raise AuthenticationError.new - end - rescue Net::IMAP::NoResponseError => noresp - # logger.error "Error on authentication!" - # logger.error noresp.backtrace.join("\n") - raise AuthenticationError.new - rescue Net::IMAP::BadResponseError => bad - # logger.error "Error on authentication!" - # logger.error bad.backtrace.join("\n") - raise AuthenticationError.new - rescue Net::IMAP::ResponseError => resp - # logger.error "Error on authentication!" - # logger.error resp.backtrace.join("\n") - raise AuthenticationError.new - end - @username = username - begin - # logger.error "IMAP authentication - #{CDF::CONFIG[:imap_auth]}." - if CDF::CONFIG[:imap_auth] == 'NOAUTH' - @imap.login(username, password) - else - @imap.authenticate(CDF::CONFIG[:imap_auth], username, password) - end - @connected = true - rescue Exception => ex - # logger.error "Error on authentication!" - # logger.error ex.backtrace.join("\n") - raise AuthenticationError.new - end - end - end - - def imap - @imap - end - - # Function chnage password works only if root has run imap_backend - # and users courier-authlib utility authtest - from courier-imap version 4.0.1 - def change_password(username, password, new_password) - ret = "" - cin, cout, cerr = Open3.popen3("/usr/sbin/authtest #{username} #{password} #{new_password}") - ret << cerr.gets - if ret.include?("Password change succeeded.") - return true - else - logger.error "[!] Error on change password! - #{ret}" - return false - end - end - - def disconnect - if @connected - @imap.logout - #@imap.disconnect - @imap = nil - @connected = false - end - end - - def [](mailboxname) - @last_folder = IMAPFolderList.new(self, @username)[mailboxname] - end - - def folders - # reference just to stop GC - @folder_list ||= IMAPFolderList.new(self, @username) - @folder_list - end - - def reload - @folder_list.reload if @folder_list - end - - def create_folder(name) -# begin - @imap.create(Net::IMAP.encode_utf7(name)) - reload -# rescue Exception=>e -# end - end - - def delete_folder(name) - begin - @imap.delete(folders[name].utf7_name) - reload - rescue Exception=>e - logger.error("Exception on delete #{name} folder #{e}") - end - end - - def message_sent(message) - # ensure we have sent folder - begin - @imap.create(CDF::CONFIG[:mail_sent]) - rescue Exception=>e - end - begin - @imap.append(CDF::CONFIG[:mail_sent], message) - folders[CDF::CONFIG[:mail_sent]].cached = false if folders[CDF::CONFIG[:mail_sent]] - rescue Exception=>e - logger.error("Error on append - #{e}") - end - - end - - def message_bulk(message) - # ensure we have sent folder - begin - @imap.create(CDF::CONFIG[:mail_bulk_sent]) - rescue Exception=>e - end - begin - @imap.append(CDF::CONFIG[:mail_bulk_sent], message) - folders[CDF::CONFIG[:mail_sent]].cached = false if folders[CDF::CONFIG[:mail_bulk_sent]] - rescue Exception=>e - logger.error("Error on bulk - #{e}") - end - end -end - -class IMAPFolderList - include Enumerable - cattr_accessor :logger - - def initialize(mailbox, username) - @mailbox = mailbox - @folders = Hash.new - @username = username - end - - def each - refresh if @folders.empty? - #@folders.each_value { |folder| yield folder } - # We want to allow sorted access; for now only (FIXME) - - @folders.sort.each { |pair| yield pair.last } - end - - def reload - refresh - end - - def [](name) - refresh if @folders.empty? - @folders[name] - end - - private - def refresh - @folders = {} - result = @mailbox.imap.list('', '*') - if result - result.each do |info| - folder = IMAPFolder.new(@mailbox, info.name, @username, info.attr, info.delim) - @folders[folder.name] = folder - end - else - # if there are no folders subscribe to INBOX - this is on first use - @mailbox.imap.subscribe(CDF::CONFIG[:mail_inbox]) - # try again to list them - we should find INBOX - @mailbox.imap.list('', '*').each do |info| - @folders[info.name] = IMAPFolder.new(@mailbox, info.name, @username, info.attr, info.delim) - end - end - @folders - end -end - -class IMAPFolder - attr_reader :mailbox - attr_reader :name - attr_reader :utf7_name - attr_reader :username - attr_reader :delim - attr_reader :attribs - - attr_writer :cached - attr_writer :mcached - - cattr_accessor :logger - - @@fetch_attr = ['ENVELOPE','BODYSTRUCTURE', 'FLAGS', 'UID', 'RFC822.SIZE'] - - def initialize(mailbox, utf7_name, username, attribs, delim) - @mailbox = mailbox - @utf7_name = utf7_name - @name = Net::IMAP.decode_utf7 utf7_name - @username = username - @messages = Array.new - @delim = delim - @attribs = attribs - @cached = false - @mcached = false - end - - def activate - if(@mailbox.selected_mailbox != @name) - @mailbox.selected_mailbox = @name - @mailbox.imap.select(@utf7_name) - load_total_unseen if !@cached - end - end - - # Just delete message without interaction with Trash folder - def delete(message) - activate - uid = (message.kind_of?(Integer) ? message : message.uid) - @mailbox.imap.uid_store(uid, "+FLAGS", :Deleted) - @mailbox.imap.expunge - # Sync with trash cannot be made - new uid generated - so just delete message from current folder - ImapMessage.delete_all(["username = ? and folder_name = ? and uid = ?", @username, @name, uid]) - @cached = false - end - - # Deleted messages - move to trash folder - def delete_multiple(uids) - # ensure we have trash folder - begin - @mailbox.imap.create(CDF::CONFIG[:mail_trash]) - rescue - end - move_multiple(uids, CDF::CONFIG[:mail_trash]) - end - - def copy(message, dst_folder) - uid = (message.kind_of?(Integer) ? message : message.uid) - activate - @mailbox.imap.uid_copy(uid, dst_folder) - @mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder] - @mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder] - end - - def copy_multiple(message_uids, dst_folder) - activate - @mailbox.imap.uid_copy(message_uids, dst_folder) - @mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder] - @mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder] - end - - def move(message, dst_folder) - uid = (message.kind_of?(Integer) ? message : message.uid) - activate - @mailbox.imap.uid_copy(uid, dst_folder) - @mailbox.imap.uid_store(uid, "+FLAGS", :Deleted) - @mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder] - @mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder] - @mailbox.imap.expunge - ImapMessage.delete_all(["username = ? and folder_name = ? and uid = ? ", @username, @name, uid]) - @cached = false - @mcached = false - end - - def move_multiple(message_uids, dst_folder) - activate - @mailbox.imap.uid_copy(message_uids, @mailbox.folders[dst_folder].utf7_name) - @mailbox.imap.uid_store(message_uids, "+FLAGS", :Deleted) - @mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder] - @mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder] - @mailbox.imap.expunge - ImapMessage.delete_all(["username = ? and folder_name = ? and uid in ( ? )", @username, @name, message_uids]) - @cached = false - @mcached = false - end - - def mark_read(message_uid) - activate - cached = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, message_uid]) - if cached.unread - cached.unread = false - cached.save - @mailbox.imap.select(@name) - @mailbox.imap.uid_store(message_uid, "+FLAGS", :Seen) - @unseen_messages = @unseen_messages - 1 - end - end - - def mark_unread(message_uid) - activate - cached = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, message_uid]) - if !cached.unread - cached.unread = true - cached.save - @mailbox.imap.select(@name) - @mailbox.imap.uid_store(message_uid, "-FLAGS", :Seen) - @unseen_messages = @unseen_messages + 1 - end - end - - def expunge - activate - @mailbox.imap.expunge - end - - def synchronize_cache(offset=0, limit = 10) - to = limit+offset - startSync = Time.now - activate - startUidFetch = Time.now - - #Count all messages - count = @mailbox.imap.fetch(1..-1, "UID") - to = count.size if count.size < to - - - range = (offset..to) - #logger.info range.inspect - - server_messages = @mailbox.imap.fetch(range, "(UID FLAGS)") - #server_messages = @mailbox.imap.uid_fetch(sequence_uids, ["UID", "FLAGS"]) - - startDbFetch = Time.now - cached_messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name]) - - cached_unread_uids = Array.new - cached_read_uids = Array.new - uids_to_be_deleted = Array.new - - cached_messages.each { |msg| - cached_unread_uids << msg.uid if msg.unread - cached_read_uids << msg.uid unless msg.unread - uids_to_be_deleted << msg.uid - } - - uids_to_be_fetched = Array.new - server_msg_uids = Array.new - - uids_unread = Array.new - uids_read = Array.new - - server_messages.each { |server_msg| - uid, flags = server_msg.attr['UID'], server_msg.attr['FLAGS'] - server_msg_uids << uid - unless uids_to_be_deleted.include?(uid) - uids_to_be_fetched << uid - else - if flags.member?(:Seen) && cached_unread_uids.include?(uid) - uids_read << uid - elsif !flags.member?(:Seen) && cached_read_uids.include?(uid) - uids_unread << uid - end - end - uids_to_be_deleted.delete(uid) - } unless server_messages.nil? - - ImapMessage.delete_all(["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_to_be_deleted]) unless uids_to_be_deleted.empty? - ImapMessage.update_all('unread = 0', ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_read]) unless uids_read.empty? - ImapMessage.update_all('unread = 1', ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_unread]) unless uids_unread.empty? - - - # fetch and store not cached messages - unless uids_to_be_fetched.empty? - # logger.debug("About to fetch #{uids_to_be_fetched.join(",")}") - uids_to_be_fetched.each_slice(20) do |slice| - fetch_uids(slice) - end - end - #FIX: @mcached = true - # logger.debug("Synchonization done for folder #{@name} in #{Time.now - startSync} ms.") - end - - def fetch_uids(uids) - imapres = @mailbox.imap.uid_fetch(uids, @@fetch_attr) - imapres.each { |cache| - envelope = cache.attr['ENVELOPE']; - message = ImapMessage.create( :folder_name => @name, - :username => @username, - :msg_id => envelope.message_id, - :uid => cache.attr['UID'], - :from_addr => envelope.from, - :to_addr => envelope.to, - :subject => envelope.subject, - :content_type => cache.attr['BODYSTRUCTURE'].multipart? ? 'multipart' : 'text', - :date => envelope.date, - :unread => !(cache.attr['FLAGS'].member? :Seen), - :size => cache.attr['RFC822.SIZE']) - } - end - - def messages(offset = 0, limit = 10, sort = 'date desc') - # Synchronize first retrieval time - synchronize_cache(offset+1, limit) #unless @mcached - - if limit == -1 - @messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name], :order => sort) - else - @messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name], :order => sort ) - end - end - - def messages_search(query = ["ALL"], sort = 'date desc') - activate - uids = @mailbox.imap.uid_search(query) - if uids.size > 1 - ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids], :order => sort ) - elsif uids.size == 1 - ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ? and uid = ? ", @username, @name, uids.first], :order => sort ) - else - return Array.new - end - - end - - def message(uid) - activate - message = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, uid]) - message.set_folder(self) - message - end - - def unseen - activate - load_total_unseen if !@cached - @unseen_messages - end - - def total - activate - load_total_unseen if !@cached - @total_messages - end - - def load_total_unseen - stat = @mailbox.imap.status(@utf7_name, ["MESSAGES", "UNSEEN"]) - @total_messages, @unseen_messages = stat["MESSAGES"], stat['UNSEEN'] - @cached = true - end - - def update_status - @status ||= @mailbox.imap.status(@utf7_name, ["MESSAGES"]) - end - - def subscribe - @mailbox.imap.subscribe(@utf7_name) - end - - def trash? - self.name == CDF::CONFIG[:mail_trash] - end -end diff --git a/lib/webmail/mail2screen.rb b/lib/webmail/mail2screen.rb deleted file mode 100755 index 5504cc0..0000000 --- a/lib/webmail/mail2screen.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'cdfutils' -module Mail2Screen - def mail2html(mail, msg_id) - footer = "" - parsed_body = create_body(mail, msg_id, footer) - - ret = "\n" - ret << "\n" - ret << " \n" - ret << " \n" - if @mail.cc_addrs - ret << " \n" - end - if @mail.bcc_addrs - ret << " \n" - end - ret << " \n" - if footer != '' - ret << " \n" - end - ret << " \n" - ret << "
#{t :from}:#{address(mail.from_addrs, @msg_id)}
#{t :to}:#{address(mail.to_addrs, @msg_id)}
#{t :cc}:#{address(mail.cc_addrs, @msg_id)}
#{t :bcc}:#{address(mail.bcc_addrs, @msg_id)}
#{t :subject}:#{h(mime_encoded?(mail.subject) ? mime_decode(mail.subject) : mail.subject)}\n" - ret << "
#{t :date}:#{h message_date(mail.date)}
#{image_tag('attachment.png')}#{footer}
\n" - - ret << "
\n" - ret << parsed_body - ret << "
\n" - end - - def create_body(mail, msg_id, footer) - charset = (mail.charset.nil? ? 'iso-8859-1' : mail.charset) - if mail.multipart? - ret = "" - if mail.content_type == 'multipart/alternative' - # take only HTML part - mail.parts.each { |part| - if part.content_type == "text/html" or part.multipart? - ret << create_body(part, msg_id, footer) - end - } - return ret - else - mail.parts.each { |part| - if part.multipart? - ret << create_body(part, msg_id, footer) - else - footer << ", " if footer != '' - footer << add_attachment(part.header['content-type'], msg_id) - if part.content_type == "text/plain" or part.content_type.nil? - charset = (part.charset.nil? ? charset : mail.charset) - ret << add_text(part, part.transfer_encoding, charset) - elsif part.content_type == "text/html" - charset = (part.charset.nil? ? charset : mail.charset) - ret << add_html(part, part.transfer_encoding, charset) - elsif part.content_type.include?("image/") - ctype = part.header['content-type'] - ret << add_image(ctype, msg_id) - elsif part.content_type.include?("message/rfc822") - ret << "
#{_('Follows attached message')}:
" << mail2html(TMail::Mail.parse(part.body), msg_id) - end - end - } - return ret - end - else - ret = "" - if mail.content_type == "text/plain" or mail.content_type.nil? - ret << add_text(mail, mail.transfer_encoding, charset) - elsif mail.content_type == "text/html" - ret << add_html(mail, mail.transfer_encoding, charset) - end - return ret - end - end - - def add_text(part, encoding, charset) - CGI.escapeHTML(decode_part_text("#{part}", encoding, charset)).gsub(/\r\n/,"
").gsub(/\r/, "
").gsub(/\n/,"
") - end - - def add_html(part, encoding, charset) - strip_html(decode_part_text("#{part}", encoding, charset)) - end - - def decode_part_text(part_str, encoding, charset) - # Parse mail - header, text = "", "" - - # Get header and body - #Content-type: text/plain; charset="ISO-8859-1" - #Content-transfer-encoding: quoted-printable - isBody = false - part_str.each_line { |line| - if isBody - text << line - else - if line.strip == "" - isBody = true - else - header << line - end - end - } - # Manage encoding - if not(encoding.nil?) and encoding.downcase == "quoted-printable" - ret = from_qp(text) - elsif not(encoding.nil?) and encoding.downcase == "base64" - ret = "#{text.unpack("m")}" - else - ret = text - end - # manage charset - if ret.nil? or charset.nil? or charset.downcase == "utf-8" - return ret - else - begin - return Iconv.conv("UTF-8",charset.downcase, ret) - rescue Exception => ex - RAILS_DEFAULT_LOGGER.debug("Exception occured #{ex}\n#{ex.backtrace.join('\n')}") - return ret - end - end - end - - def add_attachment(content_type, msg_id) - filename = (content_type.nil? or content_type['name'].nil? ? "" : content_type['name']) - if filename == "" - "" - else - " #{filename}" - end - end - - def add_image(content_type, msg_id) - filename = (content_type.nil? or content_type['name'].nil? ? "" : content_type['name']) - "

#{filename}
" - end - - def friendly_address(addr) - addr.kind_of?(Net::IMAP::Address) ? ((addr.name.nil? or addr.name.strip == "") ? "#{addr.mailbox}@#{addr.host}" : "#{(mime_encoded?(addr.name.strip) ? mime_decode(addr.name.to_s): addr.name.to_s)}<#{addr.mailbox}@#{addr.host}>") : ((addr.name.nil? or addr.name.strip == "") ? "#{addr.spec}" : "#{(mime_encoded?(addr.name.strip) ? mime_decode(addr.name.to_s): addr.name.to_s)}<#{addr.spec}>") - end - - def friendly_address_or_name(addr) - if addr.kind_of?(Net::IMAP::Address) - ((addr.name.nil? or addr.name.to_s == "") ? "#{addr.mailbox}@#{addr.host}" : (mime_encoded?(addr.name.to_s) ? mime_decode(addr.name.to_s): addr.name.to_s)) - else - ((addr.nil? or addr.to_s == "") ? "#{addr.to_s}" : (mime_encoded?(addr.to_s) ? mime_decode(addr.to_s): addr.to_s)) - end - end - - def add_to_contact(addr, msg_id) - " "+t(:add_to_contacts) - end - - def short_address(addresses) - ret = "" - addresses.each { |addr| #split(/,\s*/) - ret << "," unless ret == "" - ret << CGI.escapeHTML(friendly_address_or_name(addr)) - } unless addresses.nil? - ret - end - - def address(addresses, msg_id) - ret = "" - addresses.each { |addr| #split(/,\s*/) - ret << "," unless ret == "" - ret << CGI.escapeHTML(friendly_address_or_name(addr)) << add_to_contact(addr, msg_id) - } unless addresses.nil? - return ret - end -end diff --git a/lib/webmail/mail_transform.rb b/lib/webmail/mail_transform.rb deleted file mode 100755 index 30fe03c..0000000 --- a/lib/webmail/mail_transform.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'mail2screen' - -class MailTransform - include Mail2Screen - - def get_body(tmail, type) - @mail = tmail - footer = "" - msg_id = "" - ret = mail2html(tmail, msg_id) - ret = ret.gsub(//,"\n").gsub(/ /, " ").gsub(/</, "<").gsub(/>/, ">").gsub(/&/, "&").gsub(//, "\n").gsub(/\n/, "\n> ") if type == 'text/plain' - ret = ret.gsub(/\r\n/,"
").gsub(/\r/, "
").gsub(/\n/,"
").gsub(//, "
> ") unless type == 'text/plain' - return ret - end - - def mail2html(mail, msg_id) - footer = "" - parsed_body = create_body(mail, msg_id, footer) - - ret = "-----Original Message-----\n#{_('From')}:#{address(mail.from_addrs, @msg_id)}\n" - ret << "#{_('To')}:#{address(mail.to_addrs, @msg_id)}\n" - if @mail.cc_addrs - ret << " #{_('CC')}:#{address(mail.cc_addrs, @msg_id)}\n" - end - if @mail.bcc_addrs - ret << "#{_('BCC')}:#{address(mail.bcc_addrs, @msg_id)}\n" - end - ret << "#{_('Subject')}:#{mime_encoded?(mail.subject) ? mime_decode(mail.subject) : mail.subject}\n" - ret << "#{_('Date')}:#{message_date(mail.date)}\n" - ret << "\n" - ret << "\n" - ret << parsed_body - ret << "\n" - end - - def message_date(datestr) - t = Time.now - begin - if datestr.kind_of?(String) - d = (Time.rfc2822(datestr) rescue Time.parse(value)).localtime - else - d = datestr - end - if d.day == t.day and d.month == t.month and d.year == t.year - d.strftime("%H:%M") - else - d.strftime("%Y-%m-%d") - end - rescue - begin - d = imap2time(datestr) - if d.day == t.day and d.month == t.month and d.year == t.year - d.strftime("%H:%M") - else - d.strftime("%Y-%m-%d") - end - rescue - datestr - end - end - end - - # Overwrite some staff - def add_to_contact(addr, msg_id) - "" - end - def add_attachment(content_type, msg_id) - "" - end - - def add_image(content_type, msg_id) - "" - end - -end \ No newline at end of file diff --git a/lib/webmail/maildropserializator.rb b/lib/webmail/maildropserializator.rb deleted file mode 100755 index cc5424a..0000000 --- a/lib/webmail/maildropserializator.rb +++ /dev/null @@ -1,51 +0,0 @@ -module MaildropSerializator - def serialize_to_file - mail_drop_filter = File.new(self.mail_filter_path, "w") - for filter in filters - mail_drop_filter << "# filter '#{filter.name}'\n" - mail_drop_filter << "if (#{filter_expressions(filter)})\n" - mail_drop_filter << "{\n" - mail_drop_filter << " exception {\n" - mail_drop_filter << " to #{dest_folder(filter)}\n" - mail_drop_filter << " }\n" - mail_drop_filter << "}\n" - end - mail_drop_filter.close() - end - - private - def dest_folder(filter) - '$DEFAULT/'< 'webmail/webmail', :action => 'messages' - - diff --git a/lib/webmail/virtual_email.rb b/lib/webmail/virtual_email.rb deleted file mode 100755 index 8547cf9..0000000 --- a/lib/webmail/virtual_email.rb +++ /dev/null @@ -1,3 +0,0 @@ -class VirtualEmail < ActiveRecord::Base - def self.table_name() "cdf.wm_virtual" end -end diff --git a/public/404.html b/public/404.html old mode 100755 new mode 100644 diff --git a/public/422.html b/public/422.html old mode 100755 new mode 100644 diff --git a/public/500.html b/public/500.html old mode 100755 new mode 100644 diff --git a/public/favicon.ico b/public/favicon.ico old mode 100755 new mode 100644 diff --git a/public/images/logo3.png b/public/images/logo3.png deleted file mode 100644 index 5bd6f502a4cf4fdf3933afc67272d25f10299656..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63579 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

%Ze5G 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

oPu5!g^0a(Hli7#$E-kCQIsHf-CXZ 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 diff --git a/public/images/logo3_dark.png b/public/images/logo3_dark.png deleted file mode 100644 index 966187dc71a09eb0ea2c28a1198fada656f4f014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77962 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 E0-&sE+5i9m diff --git a/themes/original/images/rails.png b/public/images/rails.png old mode 100755 new mode 100644 similarity index 100% rename from themes/original/images/rails.png rename to public/images/rails.png diff --git a/public/robots.txt b/public/robots.txt old mode 100755 new mode 100644 diff --git a/public/javascripts/.gitkeep b/public/stylesheets/.gitkeep similarity index 100% rename from public/javascripts/.gitkeep rename to public/stylesheets/.gitkeep diff --git a/test/functional/core_controller_test.rb b/test/functional/core_controller_test.rb new file mode 100644 index 0000000..eed5126 --- /dev/null +++ b/test/functional/core_controller_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class CoreControllerTest < ActionController::TestCase + test "should get login" do + get :login + assert_response :success + end + + test "should get logout" do + get :logout + assert_response :success + end + +end diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb old mode 100755 new mode 100644 diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100755 new mode 100644 diff --git a/test/unit/helpers/core_helper_test.rb b/test/unit/helpers/core_helper_test.rb new file mode 100644 index 0000000..8a87c05 --- /dev/null +++ b/test/unit/helpers/core_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class CoreHelperTest < ActionView::TestCase +end diff --git a/themes/olive/README.olive b/themes/olive/README.olive old mode 100644 new mode 100755 diff --git a/themes/olive/images/.gitkeep b/themes/olive/images/.gitkeep old mode 100644 new mode 100755 diff --git a/themes/olive/images/key.png b/themes/olive/images/key.png old mode 100644 new mode 100755 diff --git a/themes/olive/images/logo_small.png b/themes/olive/images/logo_small.png old mode 100644 new mode 100755 diff --git a/themes/olive/javascripts/.gitkeep b/themes/olive/javascripts/.gitkeep old mode 100644 new mode 100755 diff --git a/themes/olive/javascripts/application.js b/themes/olive/javascripts/application.js new file mode 100755 index 0000000..fe45776 --- /dev/null +++ b/themes/olive/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/themes/olive/javascripts/contact_choose.js b/themes/olive/javascripts/contact_choose.js deleted file mode 100755 index fc27f07..0000000 --- a/themes/olive/javascripts/contact_choose.js +++ /dev/null @@ -1,52 +0,0 @@ - -var fieldTo = "" -var fieldToc = "" -var fieldCC = "" -var fieldBCC = "" - -function respondTo(str) { - if (fieldTo == "") fieldTo += str - else fieldTo += "," + str -} - -function respondTo(str, contactId) { - if (fieldTo == "") fieldTo += str - else fieldTo += "," + str - - if (fieldToc == "") fieldToc += contactId - else fieldToc += "," + contactId -} - -function respondCC(str) { - if (fieldCC == "") fieldCC += str - else fieldCC += "," + str -} - -function respondBCC(str) { - if (fieldBCC == "") fieldBCC += str - else fieldBCC += "," + str -} - -function respondToCaller() { - if (window.opener) { - doc = window.opener.document; - setAddrField(getFormFieldPoint(doc, 'mail_to'), fieldTo); - setAddrField(getFormFieldPoint(doc, 'mail_toc'), fieldToc); - setAddrField(getFormFieldPoint(doc, 'mail_cc'), fieldCC); - setAddrField(getFormFieldPoint(doc, 'mail_bcc'), fieldBCC); - window.close(); - } -} - -function getFormFieldPoint(doc, id) { - if ( doc.getElementById ) elem = doc.getElementById( id ); - else if ( doc.all ) elem = doc.eval( "document.all." + id ); - return elem -} - -function setAddrField(fld, value) { - if (value != "") { - if (fld.value == "") fld.value = value; - else fld.value += "," + value; - } -} \ No newline at end of file diff --git a/themes/olive/javascripts/controls.js b/themes/olive/javascripts/controls.js new file mode 100755 index 0000000..7392fb6 --- /dev/null +++ b/themes/olive/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)); + } +}); \ No newline at end of file diff --git a/themes/olive/javascripts/dragdrop.js b/themes/olive/javascripts/dragdrop.js new file mode 100755 index 0000000..15c6dbc --- /dev/null +++ b/themes/olive/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')]; +}; \ No newline at end of file diff --git a/themes/olive/javascripts/effects.js b/themes/olive/javascripts/effects.js new file mode 100755 index 0000000..c81e6c7 --- /dev/null +++ b/themes/olive/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); \ No newline at end of file diff --git a/themes/olive/javascripts/effects2.js b/themes/olive/javascripts/effects2.js deleted file mode 100755 index 6b73620..0000000 --- a/themes/olive/javascripts/effects2.js +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright (c) 2005 Thomas Fuchs (http://mir.aculo.us) -// -// 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. - - - Effect2 = {} - - /* ------------- transitions ------------- */ - - Effect2.Transitions = {} - Effect2.Transitions.linear = function(pos) { - return pos; - } - Effect2.Transitions.sinoidal = function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; - } - Effect2.Transitions.reverse = function(pos) { - return 1-pos; - } - Effect2.Transitions.flicker = function(pos) { - return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); - } - Effect2.Transitions.wobble = function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; - } - - /* ------------- core effects ------------- */ - - Effect2.Base = function() {}; - Effect2.Base.prototype = { - setOptions: function(options) { - this.options = { - transition: Effect2.Transitions.sinoidal, - duration: 1.0, // seconds - fps: 25.0, // max. 100fps - sync: false, // true for combining - from: 0.0, - to: 1.0 - }.extend(options || {}); - }, - start: function(options) { - this.setOptions(options || {}); - this.currentFrame = 0; - this.startOn = new Date().getTime(); - this.finishOn = this.startOn + (this.options.duration*1000); - if(this.options.beforeStart) this.options.beforeStart(this); - if(!this.options.sync) this.loop(); - }, - loop: function() { - timePos = new Date().getTime(); - if(timePos >= this.finishOn) { - this.render(this.options.to); - if(this.finish) this.finish(); - if(this.options.afterFinish) this.options.afterFinish(this); - return; - } - pos = (timePos - this.startOn) / (this.finishOn - this.startOn); - frame = Math.round(pos * this.options.fps * this.options.duration); - if(frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - this.timeout = setTimeout(this.loop.bind(this), 10); - }, - render: function(pos) { - if(this.options.transition) pos = this.options.transition(pos); - pos = pos * (this.options.to-this.options.from); - pos += this.options.from; - if(this.options.beforeUpdate) this.options.beforeUpdate(this); - if(this.update) this.update(pos); - if(this.options.afterUpdate) this.options.afterUpdate(this); - }, - cancel: function() { - if(this.timeout) clearTimeout(this.timeout); - } - } - - Effect2.Parallel = Class.create(); - Effect2.Parallel.prototype = (new Effect2.Base()).extend({ - initialize: function(effects) { - this.effects = effects || []; - this.start(arguments[1]); - }, - update: function(position) { - for (var i = 0; i < this.effects.length; i++) - this.effects[i].render(position); - }, - finish: function(position) { - for (var i = 0; i < this.effects.length; i++) - if(this.effects[i].finish) this.effects[i].finish(position); - } - }); - - Effect2.Opacity = Class.create(); - Effect2.Opacity.prototype = (new Effect2.Base()).extend({ - initialize: function() { - this.element = $(arguments[0] || document.rootElement); - options = { - from: 0.0, - to: 1.0 - }.extend(arguments[1] || {}); - this.start(options); - }, - update: function(position) { - this.setOpacity(position); - }, - setOpacity: function(opacity) { - opacity = (opacity == 1) ? 0.99999 : opacity; - this.element.style.opacity = opacity; - this.element.style.filter = "alpha(opacity:"+opacity*100+")"; - } - }); - - Effect2.MoveBy = Class.create(); - Effect2.MoveBy.prototype = (new Effect2.Base()).extend({ - initialize: function(element, toTop, toLeft) { - this.element = $(element); - this.originalTop = - this.element.style.top ? parseFloat(this.element.style.top) : 0; - this.originalLeft = - this.element.style.left ? parseFloat(this.element.style.left) : 0; - this.toTop = toTop; - this.toLeft = toLeft; - if(this.element.style.position == "") - this.element.style.position = "relative"; - this.start(arguments[3]); - }, - update: function(position) { - topd = this.toTop * position + this.originalTop; - leftd = this.toLeft * position + this.originalLeft; - this.setPosition(topd, leftd); - }, - setPosition: function(topd, leftd) { - this.element.style.top = topd + "px"; - this.element.style.left = leftd + "px"; - } - }); - - Effect2.Scale = Class.create(); - Effect2.Scale.prototype = (new Effect2.Base()).extend({ - initialize: function(element, percent) { - this.element = $(element) - options = { - scaleX: true, - scaleY: true, - scaleContent: true, - scaleFromCenter: false, - scaleMode: 'box', // 'box' or 'contents' - scaleFrom: 100.0 - }.extend(arguments[2] || {}); - this.originalTop = this.element.offsetTop; - this.originalLeft = this.element.offsetLeft; - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - this.factor = (percent/100.0) - (options.scaleFrom/100.0); - if(options.scaleMode=='box') { - this.originalHeight = this.element.clientHeight; - this.originalWidth = this.element.clientWidth; - } else - if(options.scaleMode=='contents') { - this.originalHeight = this.element.scrollHeight; - this.originalWidth = this.element.scrollWidth; - } - this.start(options); - }, - - update: function(position) { - currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if(this.options.scaleContent && this.sizeEm) - this.element.style.fontSize = this.sizeEm*currentScale + "em"; - this.setDimensions( - this.originalWidth * currentScale, - this.originalHeight * currentScale); - }, - - setDimensions: function(width, height) { - if(this.options.scaleX) this.element.style.width = width + 'px'; - if(this.options.scaleY) this.element.style.height = height + 'px'; - if(this.options.scaleFromCenter) { - topd = (height - this.originalHeight)/2; - leftd = (width - this.originalWidth)/2; - if(this.element.style.position=='absolute') { - if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; - if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; - } else { - if(this.options.scaleY) this.element.style.top = -topd + "px"; - if(this.options.scaleX) this.element.style.left = -leftd + "px"; - } - } - } - }); - - /* ------------- prepackaged effects ------------- */ - - Effect2.Fade = function(element) { - options = { - from: 1.0, - to: 0.0, - afterFinish: function(effect) - { Element.hide(effect.element); - effect.setOpacity(1); } - }.extend(arguments[1] || {}); - new Effect2.Opacity(element,options); - } - -Effect2.Appear = function(element) { - options = { - from: 0.0, - to: 1.0, - beforeStart: function(effect) - { effect.setOpacity(0); - Element.show(effect.element); }, - afterUpdate: function(effect) - { Element.show(effect.element); } - }.extend(arguments[1] || {}); - new Effect2.Opacity(element,options); -} - - Effect2.Puff = function(element) { - new Effect2.Parallel( - [ new Effect2.Scale(element, 200, { sync: true, scaleFromCenter: true }), - new Effect2.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 1.0, - afterUpdate: function(effect) - { effect.effects[0].element.style.position = 'absolute'; }, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - } - ); - } - - Effect2.BlindUp = function(element) { - $(element).style.overflow = 'hidden'; - new Effect2.Scale(element, 0, - { scaleContent: false, - scaleX: false, - afterFinish: function(effect) - { Element.hide(effect.element) } - }.extend(arguments[1] || {}) - ); - } - - Effect2.BlindDown = function(element) { - $(element).style.height = '0px'; - $(element).style.overflow = 'hidden'; - Element.show(element); - new Effect2.Scale(element, 100, - { scaleContent: false, - scaleX: false, - scaleMode: 'contents', - scaleFrom: 0 - }.extend(arguments[1] || {}) - ); - } - - Effect2.SwitchOff = function(element) { - new Effect2.Appear(element, - { duration: 0.4, - transition: Effect2.Transitions.flicker, - afterFinish: function(effect) - { effect.element.style.overflow = 'hidden'; - new Effect2.Scale(effect.element, 1, - { duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, - afterUpdate: function(effect) { - if(effect.element.style.position=="") - effect.element.style.position = 'relative'; }, - afterFinish: function(effect) { Element.hide(effect.element); } - } ) - } - } ) - } - - Effect2.DropOut = function(element) { - new Effect2.Parallel( - [ new Effect2.MoveBy(element, 100, 0, { sync: true }), - new Effect2.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 0.5, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - }); - } - - Effect2.Shake = function(element) { - new Effect2.MoveBy(element, 0, 20, - { duration: 0.05, afterFinish: function(effect) { - new Effect2.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { - new Effect2.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { - new Effect2.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { - new Effect2.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { - new Effect2.MoveBy(effect.element, 0, -20, - { duration: 0.05, afterFinish: function(effect) { - }}) }}) }}) }}) }}) }}); - } - - Effect2.SlideDown = function(element) { - $(element).style.height = '0px'; - $(element).style.overflow = 'hidden'; - $(element).firstChild.style.position = 'relative'; - Element.show(element); - new Effect2.Scale(element, 100, - { scaleContent: false, - scaleX: false, - scaleMode: 'contents', - scaleFrom: 0, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; } - }.extend(arguments[1] || {}) - ); - } - - Effect2.SlideUp = function(element) { - $(element).style.overflow = 'hidden'; - $(element).firstChild.style.position = 'relative'; - Element.show(element); - new Effect2.Scale(element, 0, - { scaleContent: false, - scaleX: false, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { Element.hide(effect.element); } - }.extend(arguments[1] || {}) - ); - } \ No newline at end of file diff --git a/themes/olive/javascripts/global.js b/themes/olive/javascripts/global.js deleted file mode 100755 index 1176552..0000000 --- a/themes/olive/javascripts/global.js +++ /dev/null @@ -1,216 +0,0 @@ -function changeLoc(loc) { - window.location = loc -} -function getCookie(name) { - var prefix = name + "="; - var cStr = document.cookie; - var start = cStr.indexOf(prefix); - if (start == -1) { - return null; - } - var end = cStr.indexOf(";", start + prefix.length); - if (end == -1) { - end = cStr.length; - } - var value = cStr.substring(start + prefix.length, end); - return unescape(value); -} -function setCookie(name, value, expiration) { - document.cookie = name + "=" + value + "; expires=" + expiration; -} -function toggleCheckbox(checkBox) { - var element = document.getElementById(checkBox.id); - if (element.value == "1" || element.checked) { - element.checked = false; - element.value = "0"; - } else { - element.checked = true; - element.value = "1"; - } -} -function toggleChkbox(checkBox) { - if (checkBox.checked) { - checkBox.checked = true; - } else { - checkBox.checked = false; - } -} -function toggle_list(id) { - ul = "ul_" + id; - img = "img_" + id; - hid = "h_" + id; - ulElement = document.getElementById(ul); - imgElement = document.getElementById(img); - hiddenElement = document.getElementById(hid); - if (ulElement) { - if (ulElement.className == 'closed') { - ulElement.className = "open"; - imgElement.src = "/themes/original/images/list_opened.gif"; - hiddenElement.value = "1" - } else { - ulElement.className = "closed"; - imgElement.src = "/themes/original/images/list_closed.gif"; - hiddenElement.value = "0" - } - } -} -function toggle_layer(id) { - lElement = document.getElementById(id); - imgElement = document.getElementById("img_" + id); - if (lElement) { - if (lElement.className == 'closed') { - lElement.className = "open"; - imgElement.src = "/themes/original/images/list_opened.gif"; - return true; - } else { - lElement.className = "closed"; - imgElement.src = "/themes/original/images/list_closed.gif"; - return false; - } - } - return true; -} -function toggle_layer_status(id) { - lElement = document.getElementById(id); - if (lElement) { - if (lElement.className == 'closed') { - return false; - } else { - return true; - } - } - return true; -} -function toggle_text(id) { - if (document.getElementById) elem = document.getElementById(id); - else if (document.all) elem = eval("document.all." + id); - else return false; - if (!elem) return true; - elemStyle = elem.style; - if (elemStyle.display != "block") { - elemStyle.display = "block" - } else { - elemStyle.display = "none" - } - return true; -} -function getFF(id) { - if (document.getElementById) elem = document.getElementById(id); - else if (document.all) elem = document.eval("document.all." + id); - return elem -} -function setFF(id, value) { - if (getFF(id)) getFF(id).value = value; -} -function setCFF(id) { - if (getFF(id)) getFF(id).checked = true; -} -function updateSUFromC(btnName) { - var suem = getCookie('_cdf_em'); - var sueg = getCookie('_cdf_gr'); - if (suem != "" && suem != null && suem != "undefined") { - setFF('sup_email', suem); - setFF('signup_submit_button', btnName); - } - if (sueg && sueg != "") { - if (sueg.indexOf(",") < 0 && sueg != "") { - gr_id = sueg; - setCFF('supgr_' + gr_id); - } else while ((i = sueg.indexOf(",")) >= 0) { - gr_id = sueg.substring(0, i); - sueg = sueg.substring(i + 1); - setCFF('supgr_' + gr_id); - } - if (sueg.indexOf(",") < 0 && sueg != "") { - gr_id = sueg; - setCFF('supgr_' + gr_id); - } - } -} -function updateLUEfC() { - var suem = getCookie('_cdf_em'); - if (suem != "" && suem != null && suem != "undefined") { - setFF('login_user_email', suem); - } -} -function replaceHRFST(ifrm) { - var o = ifrm; - var w = null; - if (o.contentWindow) { - w = o.contentWindow; - } else if (window.frames && window.frames[o.id].window) { - w = window.frames[o.id]; - } else return; - var doc = w.document; - if (!doc.getElementsByTagName) return; - var anchors = doc.getElementsByTagName("a"); - for (var i = 0; i < anchors.length; i++) { - var anchor = anchors[i]; - if (anchor.getAttribute("href")) anchor.target = "_top"; - } - iHeight = doc.body.scrollHeight; - ifrm.style.height = iHeight + "px" -} -function rs(n, u, w, h, x) { - args = "width=" + w + ",height=" + h + ",resizable=yes,scrollbars=yes,status=0"; - remote = window.open(u, n, args); - if (remote != null && remote.opener == null) remote.opener = self; - if (x == 1) return remote; -} -function wizard_step_onclick(direction, alt_url) { - if (document.forms[0]) { - direction_elem = ''; - if (document.getElementById) { - direction_elem = document.getElementById('wiz_dir'); - } else if (document.all) { - direction_elem = document.eval("document.all.wiz_dir"); - } - if (direction_elem) { - direction_elem.value = direction; - } - if (document.forms[0].onsubmit) { - document.forms[0].onsubmit(); - } - document.forms[0].submit(); - } else { - window.location = alt_url; - } -} -function toggle_adtype(ad_type) { - toggle_text('upload_banner_label'); - toggle_text('upload_banner'); - toggle_text('radio1_label'); - toggle_text('radio1'); - toggle_text('radio2_label'); - toggle_text('radio2'); - toggle_text('adtitle_label'); - toggle_text('adtitle'); - toggle_text('adtext_label'); - toggle_text('adtext'); - toggle_text('banner_size_label'); - toggle_text('banner_size'); -} -function show_date_as_local_time() { - var spans = document.getElementsByTagName('span'); - for (var i = 0; i < spans.length; i++) if (spans[i].className.match(/\bLOCAL_TIME\b/i)) { - system_date = new Date(Date.parse(spans[i].innerHTML)); - if (system_date.getHours() >= 12) { - adds = ' PM'; - h = system_date.getHours() - 12; - } else { - adds = ' AM'; - h = system_date.getHours(); - } - spans[i].innerHTML = h + ":" + (system_date.getMinutes() + "").replace(/\b(\d)\b/g, '0$1') + adds; - } -} -function PopupPic(sPicURL, sWidth, sHeight) { - window.open("/popup.htm?" + sPicURL, "", "resizable=1,HEIGHT=" + sHeight + ",WIDTH=" + sWidth + ",scrollbars=yes"); -} -function open_link(target, location) { - if (target == 'blank') { - window.open(location); - } else { - window.top.location = location; - } -} diff --git a/themes/olive/javascripts/global_src.js b/themes/olive/javascripts/global_src.js deleted file mode 100755 index 76b3fb7..0000000 --- a/themes/olive/javascripts/global_src.js +++ /dev/null @@ -1,201 +0,0 @@ -function changeLoc(loc) { window.location = loc } -function getCookie(name) { - var prefix = name + "="; - var cStr = document.cookie; - var start = cStr.indexOf(prefix); - if (start==-1) { - return null; - } - - var end = cStr.indexOf(";", start+prefix.length); - if (end==-1) { end=cStr.length; } - - var value=cStr.substring(start+prefix.length, end); - return unescape(value); -} -function setCookie(name, value, expiration) { - document.cookie = name+"="+value+"; expires="+expiration; -} -function toggleCheckbox(checkBox) { - var element = document.getElementById(checkBox.id); - if (element.value == "1" || element.checked) { - element.checked = false; - element.value = "0"; - } else { - element.checked = true; - element.value = "1"; - } -} -function toggleChkbox(checkBox) { - if (checkBox.checked) { - checkBox.checked = true; - } else { - checkBox.checked = false; - } -} -function toggle_list(id){ - ul = "ul_" + id; - img = "img_" + id; - hid = "h_" + id; - ulElement = document.getElementById(ul); - imgElement = document.getElementById(img); - hiddenElement = document.getElementById(hid); - if (ulElement){ - if (ulElement.className == 'closed'){ - ulElement.className = "open"; - imgElement.src = "/themes/original/images/list_opened.gif"; - hiddenElement.value = "1" - }else{ - ulElement.className = "closed"; - imgElement.src = "/themes/original/images/list_closed.gif"; - hiddenElement.value = "0" - } - } -} -function toggle_layer(id) { - lElement = document.getElementById(id); - imgElement = document.getElementById("img_" + id); - if (lElement){ - if (lElement.className == 'closed'){ - lElement.className = "open"; - imgElement.src = "/themes/original/images/list_opened.gif"; - return true; - }else{ - lElement.className = "closed"; - imgElement.src = "/themes/original/images/list_closed.gif"; - return false; - } - } - return true; -} -function toggle_layer_status(id) { - lElement = document.getElementById(id); - if (lElement){ - if (lElement.className == 'closed'){ - return false; - }else{ - return true; - } - } - return true; -} -function toggle_text(id){ - if ( document.getElementById ) - elem = document.getElementById( id ); - else if ( document.all ) - elem = eval( "document.all." + id ); - else - return false; - - if(!elem) return true; - - elemStyle = elem.style; - if ( elemStyle.display != "block" ) { - elemStyle.display = "block" - } else { - elemStyle.display = "none" - } - return true; -} -function getFF(id) { - if ( document.getElementById ) elem = document.getElementById( id ); - else if ( document.all ) elem = document.eval( "document.all." + id ); - return elem -} -function setFF(id, value) {if(getFF(id))getFF(id).value=value;} -function setCFF(id) {if(getFF(id))getFF(id).checked=true;} -function updateSUFromC(btnName) { - var suem = getCookie('_cdf_em');var sueg = getCookie('_cdf_gr'); - if (suem != "" && suem != null && suem != "undefined") { setFF('sup_email', suem); setFF('signup_submit_button',btnName); } - - if (sueg && sueg != "") { - if (sueg.indexOf(",") < 0 && sueg != "") { gr_id = sueg; setCFF('supgr_'+gr_id); - } else while ((i = sueg.indexOf(",")) >= 0) { gr_id = sueg.substring(0,i); sueg = sueg.substring(i+1); setCFF('supgr_'+gr_id); } - if (sueg.indexOf(",") < 0 && sueg != "") { gr_id = sueg; setCFF('supgr_'+gr_id);} - } -} -function updateLUEfC() { - var suem = getCookie('_cdf_em'); - if (suem != "" && suem != null && suem != "undefined") { setFF('login_user_email', suem); } -} -function replaceHRFST(ifrm) { - var o = ifrm; - var w = null; - if (o.contentWindow) { - // For IE5.5 and IE6 - w = o.contentWindow; - } else if (window.frames && window.frames[o.id].window) { - w = window.frames[o.id]; - } else return; - var doc = w.document; - if (!doc.getElementsByTagName) return; - var anchors = doc.getElementsByTagName("a"); - for (var i=0; i= 12) { adds = ' PM'; h = system_date.getHours() - 12; } - else { adds = ' AM'; h = system_date.getHours(); } - spans[i].innerHTML = h + ":" + (system_date.getMinutes()+"").replace(/\b(\d)\b/g, '0$1') + adds; - } -} -function PopupPic(sPicURL,sWidth,sHeight) { - window.open( "/popup.htm?"+sPicURL, "", "resizable=1,HEIGHT="+sHeight+",WIDTH="+sWidth+",scrollbars=yes"); -} - -function open_link(target, location){ - if (target == 'blank'){ - window.open(location); - } else { - window.top.location = location; - } -} diff --git a/themes/olive/javascripts/htmlstyle.js b/themes/olive/javascripts/htmlstyle.js deleted file mode 100755 index ece3392..0000000 --- a/themes/olive/javascripts/htmlstyle.js +++ /dev/null @@ -1,21 +0,0 @@ -var config = new HTMLArea.Config(); // create a new configuration object - // having all the default values -config.width = '520px'; -config.pageStyle = - 'body { font-family: verdana,sans-serif; font-size: 12px } '; - -config.toolbar = [ -[ "fontname", "fontsize","formatblock","bold", "italic", "underline", "separator", "insertimage", "createlink"], -["justifyleft", "justifycenter", "justifyright", "justifyfull", "separator", "forecolor", "hilitecolor", "separator", "popupeditor", "htmlmode"] -]; -config.statusBar = false; - -var configView = new HTMLArea.Config(); // create a new configuration object - // having all the default values -configView.width = '670px'; -configView.pageStyle = - 'body { font-family: verdana,sans-serif; font-size: 12px } '; - -configView.toolbar = []; -configView.statusBar = false; -configView.readonly = true; \ No newline at end of file diff --git a/themes/olive/javascripts/jstrim.pl b/themes/olive/javascripts/jstrim.pl deleted file mode 100755 index 7829efc..0000000 --- a/themes/olive/javascripts/jstrim.pl +++ /dev/null @@ -1,76 +0,0 @@ -#! /usr/bin/perl -w - -jsTrim("prototype_src.js", "prototype.js"); -jsTrim("global_src.js", "global.js"); -#jsTrim("jscripts/tiny_mce/themes/simple/editor_template_src.js", "jscripts/tiny_mce/themes/simple/editor_template.js"); -#jsTrim("jscripts/tiny_mce/themes/default/editor_template_src.js", "jscripts/tiny_mce/themes/default/editor_template.js"); -#jsTrim("jscripts/tiny_mce/themes/advanced/editor_template_src.js", "jscripts/tiny_mce/themes/advanced/editor_template.js"); -#jsTrim("jscripts/tiny_mce/plugins/advhr/editor_plugin_src.js", "jscripts/tiny_mce/plugins/advhr/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/advimage/editor_plugin_src.js", "jscripts/tiny_mce/plugins/advimage/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/advlink/editor_plugin_src.js", "jscripts/tiny_mce/plugins/advlink/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/emotions/editor_plugin_src.js", "jscripts/tiny_mce/plugins/emotions/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/flash/editor_plugin_src.js", "jscripts/tiny_mce/plugins/flash/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/iespell/editor_plugin_src.js", "jscripts/tiny_mce/plugins/iespell/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/insertdatetime/editor_plugin_src.js", "jscripts/tiny_mce/plugins/insertdatetime/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/preview/editor_plugin_src.js", "jscripts/tiny_mce/plugins/preview/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/print/editor_plugin_src.js", "jscripts/tiny_mce/plugins/print/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/save/editor_plugin_src.js", "jscripts/tiny_mce/plugins/save/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/searchreplace/editor_plugin_src.js", "jscripts/tiny_mce/plugins/searchreplace/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/zoom/editor_plugin_src.js", "jscripts/tiny_mce/plugins/zoom/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/table/editor_plugin_src.js", "jscripts/tiny_mce/plugins/table/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/contextmenu/editor_plugin_src.js", "jscripts/tiny_mce/plugins/contextmenu/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/paste/editor_plugin_src.js", "jscripts/tiny_mce/plugins/paste/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/fullscreen/editor_plugin_src.js", "jscripts/tiny_mce/plugins/fullscreen/editor_plugin.js"); -#jsTrim("jscripts/tiny_mce/plugins/directionality/editor_plugin_src.js", "jscripts/tiny_mce/plugins/directionality/editor_plugin.js"); - -sub jsTrim { - my $inFile = $_[0]; - my $outFile = $_[1]; - my $comment = ''; - my $content = ''; - - # Load input file - open(FILE, "<$inFile"); - undef $/; - $content = ; - close(FILE); - - if ($content =~ s#^\s*(/\*.*?\*/)##s or $content =~ s#^\s*(//.*?)\n\s*[^/]##s) { - $comment = "$1\n"; - } - - local $^W; - - # removing C/C++ - style comments: - $content =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//[^\n]*|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#$2#gs; - - # save string literals - my @strings = (); - $content =~ s/("(\\.|[^"\\])*"|'(\\.|[^'\\])*')/push(@strings, "$1");'__CMPRSTR_'.$#strings.'__';/egs; - - # remove C-style comments - $content =~ s#/\*.*?\*/##gs; - # remove C++-style comments - $content =~ s#//.*?\n##gs; - # removing leading/trailing whitespace: - #$content =~ s#(?:(?:^|\n)\s+|\s+(?:$|\n))##gs; - # removing newlines: - #$content =~ s#\r?\n##gs; - - # removing other whitespace (between operators, etc.) (regexp-s stolen from Mike Hall's JS Crunchinator) - $content =~ s/\s+/ /gs; # condensing whitespace - #$content =~ s/^\s(.*)/$1/gs; # condensing whitespace - #$content =~ s/(.*)\s$/$1/gs; # condensing whitespace - $content =~ s/\s([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])/$1/gs; - $content =~ s/([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])\s/$1/gs; - - # restore string literals - $content =~ s/__CMPRSTR_([0-9]+)__/$strings[$1]/egs; - - # Write to ouput file - open(FILE, ">$outFile"); - flock(FILE, 2); - seek(FILE, 0, 2); - print FILE $comment, $content; - close(FILE); -} diff --git a/themes/olive/javascripts/prototype.js b/themes/olive/javascripts/prototype.js new file mode 100755 index 0000000..06249a6 --- /dev/null +++ b/themes/olive/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/themes/olive/javascripts/prototype_src.js b/themes/olive/javascripts/prototype_src.js deleted file mode 100755 index 87aa729..0000000 --- a/themes/olive/javascripts/prototype_src.js +++ /dev/null @@ -1,521 +0,0 @@ -var Prototype = { - Version: '1.2.1' -}; - -var Class = { - create: function() { - return function() { - this.initialize.apply(this, arguments); - } - } -}; - -var Abstract = new Object(); -Object.prototype.extend = function(object) { - for (property in object) { - this[property] = object[property]; - } - return this; -}; -Function.prototype.bind = function(object) { - var method = this; - return function() { - method.apply(object, arguments); - } -}; - -Function.prototype.bindAsEventListener = function(object) { - var method = this; - return function(event) { - method.call(object, event || window.event); - } -}; - -Number.prototype.toColorPart = function() { - var digits = this.toString(16); - if (this < 16) return '0' + digits; - return digits; -}; - -var Try = { - these: function() { - var returnValue; - - for (var i = 0; i < arguments.length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) {} - } - - return returnValue; - } -}; - -var PeriodicalExecuter = Class.create(); -PeriodicalExecuter.prototype = { - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.callback(); - } finally { - this.currentlyExecuting = false; - } - } - - this.registerCallback(); - } -}; -function $() { - var elements = new Array(); - - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); - - if (arguments.length == 1) - return element; - - elements.push(element); - } - - return elements; -}; -if (!Array.prototype.push) { - Array.prototype.push = function() { - var startLength = this.length; - for (var i = 0; i < arguments.length; i++) - this[startLength + i] = arguments[i]; - return this.length; - }; -}; - -if (!Function.prototype.apply) { - // Based on code from http://www.youngpup.net/ - Function.prototype.apply = function(object, parameters) { - var parameterStrings = new Array(); - if (!object) object = window; - if (!parameters) parameters = new Array(); - - for (var i = 0; i < parameters.length; i++) - parameterStrings[i] = 'x[' + i + ']'; - - object.__apply__ = this; - var result = eval('obj.__apply__(' + - parameterStrings[i].join(', ') + ')'); - object.__apply__ = null; - - return result; - }; -}; - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} - ) || false; - }, - - emptyFunction: function() {} -}; - -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { - this.options = { - method: 'post', - asynchronous: true, - parameters: '' - }.extend(options || {}); - } -}; - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Request.prototype = (new Ajax.Base()).extend({ - initialize: function(url, options) { - this.transport = Ajax.getTransport(); - this.setOptions(options); - - try { - if (this.options.method == 'get') - url += '?' + this.options.parameters + '&_='; - - this.transport.open(this.options.method, url, - this.options.asynchronous); - - if (this.options.asynchronous) { - this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); - } - - this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version); - - if (this.options.method == 'post') { - this.transport.setRequestHeader('Connection', 'close'); - this.transport.setRequestHeader('Content-type', - 'application/x-www-form-urlencoded'); - } - - this.transport.send(this.options.method == 'post' ? - this.options.parameters + '&_=' : null); - - } catch (e) { - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState != 1) - this.respondToReadyState(this.transport.readyState); - }, - - respondToReadyState: function(readyState) { - var event = Ajax.Request.Events[readyState]; - (this.options['on' + event] || Ajax.emptyFunction)(this.transport); - } -}); - -Ajax.Updater = Class.create(); -Ajax.Updater.prototype = (new Ajax.Base()).extend({ - initialize: function(container, url, options) { - this.container = $(container); - this.setOptions(options); - - if (this.options.asynchronous) { - this.onComplete = this.options.onComplete; - this.options.onComplete = this.updateContent.bind(this); - } - - this.request = new Ajax.Request(url, this.options); - - if (!this.options.asynchronous) - this.updateContent(); - }, - - updateContent: function() { - if (this.options.insertion) { - new this.options.insertion(this.container, - this.request.transport.responseText); - } else { - this.container.innerHTML = this.request.transport.responseText; - } - - if (this.onComplete) { - setTimeout((function() {this.onComplete(this.request)}).bind(this), 10); - } - } -}); -var Field = { - clear: function() { - for (var i = 0; i < arguments.length; i++) - $(arguments[i]).value = ''; - }, - - focus: function(element) { - $(element).focus(); - }, - - present: function() { - for (var i = 0; i < arguments.length; i++) - if ($(arguments[i]).value == '') return false; - return true; - }, - - select: function(element) { - $(element).select(); - }, - - activate: function(element) { - $(element).focus(); - $(element).select(); - } -}; -var Form = { - serialize: function(form) { - var elements = Form.getElements($(form)); - var queryComponents = new Array(); - - for (var i = 0; i < elements.length; i++) { - var queryComponent = Form.Element.serialize(elements[i]); - if (queryComponent) - queryComponents.push(queryComponent); - } - - return queryComponents.join('&'); - }, - - getElements: function(form) { - form = $(form); - var elements = new Array(); - - for (tagName in Form.Element.Serializers) { - var tagElements = form.getElementsByTagName(tagName); - for (var j = 0; j < tagElements.length; j++) - elements.push(tagElements[j]); - } - return elements; - }, - - disable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - element.blur(); - element.disable = 'true'; - } - }, - - focusFirstElement: function(form) { - form = $(form); - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - if (element.type != 'hidden' && !element.disabled) { - Field.activate(element); - break; - } - } - }, - - reset: function(form) { - $(form).reset(); - } -}; - -Form.Element = { - serialize: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return encodeURIComponent(parameter[0]) + '=' + - encodeURIComponent(parameter[1]); - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return parameter[1]; - } -}; - -Form.Element.Serializers = { - input: function(element) { - switch (element.type.toLowerCase()) { - case 'hidden': - case 'password': - case 'text': - return Form.Element.Serializers.textarea(element); - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element); - } - return false; - }, - - inputSelector: function(element) { - if (element.checked) - return [element.name, element.value]; - }, - - textarea: function(element) { - return [element.name, element.value]; - }, - - select: function(element) { - var index = element.selectedIndex; - var value = element.options[index].value || element.options[index].text; - return [element.name, (index >= 0) ? value : '']; - } -}; - -var $F = Form.Element.getValue; - -Abstract.TimedObserver = function() {}; -Abstract.TimedObserver.prototype = { - initialize: function(element, frequency, callback) { - this.frequency = frequency; - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - this.registerCallback(); - }, - - registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - - this.registerCallback(); - } -}; - -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.serialize(this.element); - } -}); - -document.getElementsByClassName = function(className) { - var children = document.getElementsByTagName('*') || document.all; - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var classNames = child.className.split(' '); - for (var j = 0; j < classNames.length; j++) { - if (classNames[j] == className) { - elements.push(child); - break; - } - } - } - - return elements; -}; - -var Element = { - toggle: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); - } - }, - - hide: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = 'none'; - } - }, - - show: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = ''; - } - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - }, - - getHeight: function(element) { - element = $(element); - return element.offsetHeight; - } -}; - -var Toggle = new Object(); -Toggle.display = Element.toggle; - -Abstract.Insertion = function(adjacency) { - this.adjacency = adjacency; -}; - -Abstract.Insertion.prototype = { - initialize: function(element, content) { - this.element = $(element); - this.content = content; - - if (this.adjacency && this.element.insertAdjacentHTML) { - this.element.insertAdjacentHTML(this.adjacency, this.content); - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.fragment = this.range.createContextualFragment(this.content); - this.insertContent(); - } - } -}; - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, this.element); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function() { - this.element.insertBefore(this.fragment, this.element.firstChild); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function() { - this.element.appendChild(this.fragment); - } -}); - -Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, - this.element.nextSibling); - } -}); \ No newline at end of file diff --git a/themes/olive/javascripts/rails.js b/themes/olive/javascripts/rails.js new file mode 100755 index 0000000..aed6aed --- /dev/null +++ b/themes/olive/javascripts/rails.js @@ -0,0 +1,191 @@ +(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; + }); + }); + + Ajax.Responders.register({ + onCreate: function(request) { + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + } + }); +})(); diff --git a/themes/olive/javascripts/scriptaculous.js b/themes/olive/javascripts/scriptaculous.js deleted file mode 100755 index cd0e341..0000000 --- a/themes/olive/javascripts/scriptaculous.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// 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. - -var Scriptaculous = { - Version: '1.5_rc3', - require: function(libraryName) { - // inserting via DOM fails in Safari 2.0, so brute force approach - document.write(''); - }, - load: function() { - if((typeof Prototype=='undefined') || - parseFloat(Prototype.Version.split(".")[0] + "." + - Prototype.Version.split(".")[1]) < 1.4) - throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0"); - var scriptTags = document.getElementsByTagName("script"); - for(var i=0;i this.maximum) sliderValue = this.maximum; - if(sliderValue < this.minimum) sliderValue = this.minimum; - var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment; - - if(this.isVertical()){ - this.setCurrentTop(offsetDiff + this.currentTop()); - } else { - this.setCurrentLeft(offsetDiff + this.currentLeft()); - } - this.value = sliderValue; - this.updateFinished(); - }, - minimumOffset: function(){ - return(this.isVertical() ? - this.trackTop() + this.alignY : - this.trackLeft() + this.alignX); - }, - maximumOffset: function(){ - return(this.isVertical() ? - this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment : - this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment); - }, - isVertical: function(){ - return (this.axis == 'vertical'); - }, - startDrag: function(event) { - if(Event.isLeftClick(event)) { - if(!this.disabled){ - this.active = true; - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var offsets = Position.cumulativeOffset(this.handle); - this.offsetX = (pointer[0] - offsets[0]); - this.offsetY = (pointer[1] - offsets[1]); - this.originalLeft = this.currentLeft(); - this.originalTop = this.currentTop(); - } - Event.stop(event); - } - }, - update: function(event) { - if(this.active) { - if(!this.dragging) { - var style = this.handle.style; - this.dragging = true; - if(style.position=="") style.position = "relative"; - style.zIndex = this.options.zindex; - } - this.draw(event); - // fix AppleWebKit rendering - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); - Event.stop(event); - } - }, - draw: function(event) { - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var offsets = Position.cumulativeOffset(this.handle); - - offsets[0] -= this.currentLeft(); - offsets[1] -= this.currentTop(); - - // Adjust for the pointer's position on the handle - pointer[0] -= this.offsetX; - pointer[1] -= this.offsetY; - var style = this.handle.style; - - if(this.isVertical()){ - if(pointer[1] > this.maximumOffset()) - pointer[1] = this.maximumOffset(); - if(pointer[1] < this.minimumOffset()) - pointer[1] = this.minimumOffset(); - - // Increment by values - if(this.values){ - this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum); - pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment; - } else { - this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum; - } - style.top = pointer[1] - offsets[1] + "px"; - } else { - if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset(); - if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset(); - // Increment by values - if(this.values){ - this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum); - pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment; - } else { - this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum; - } - style.left = (pointer[0] - offsets[0]) + "px"; - } - if(this.options.onSlide) this.options.onSlide(this.value); - }, - endDrag: function(event) { - if(this.active && this.dragging) { - this.finishDrag(event, true); - Event.stop(event); - } - this.active = false; - this.dragging = false; - }, - finishDrag: function(event, success) { - this.active = false; - this.dragging = false; - this.handle.style.zIndex = this.originalZ; - this.originalLeft = this.currentLeft(); - this.originalTop = this.currentTop(); - this.updateFinished(); - }, - updateFinished: function() { - if(this.options.onChange) this.options.onChange(this.value); - }, - keyPress: function(event) { - if(this.active && !this.disabled) { - switch(event.keyCode) { - case Event.KEY_ESC: - this.finishDrag(event, false); - Event.stop(event); - break; - } - if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); - } - } -} diff --git a/themes/olive/javascripts/webmail.js b/themes/olive/javascripts/webmail.js deleted file mode 100755 index 3abc2dd..0000000 --- a/themes/olive/javascripts/webmail.js +++ /dev/null @@ -1,35 +0,0 @@ -function chooseContacts(url) { - rs('', url + '?mode=choose',550,480,0); -} - -function setBulk() { - if (getFormField("mail_bulk").checked) getFormField("set_bulk").value = "set_bulk" - document.forms['composeMail'].submit(); -} - -function getFormField(id) { - if ( document.getElementById ) elem = document.getElementById( id ); - else if ( document.all ) elem = document.eval( "document.all." + id ); - return elem; -} - -function toggle_msg_operations(setc) { - var isOpened = toggle_layer('msgops'); - if (setc) setCookie("_wmlmo", ( isOpened ? "opened" : "closed"), 1000000); -} - -function toggle_msg_search(setc) { - var isOpened = toggle_layer('msg_search'); - if (setc) setCookie("_wmlms", (isOpened ? "opened" : "closed"), 1000000); -} - -function checkAll(theForm) { // check all the checkboxes in the list - for (var i=0;i+|z`)L+!vF*z zGZ7L8!3?o^j5=eR#T^>)O%c!nNlo%4(jx!l&hXA-#6)>L5e<}ZWAqD=`k hs?!fW)8Oc3io2}!S<%=_('Edit/Create Contact Group')%> - <%= - form_tag( - link_save, - 'method' => 'post', - 'class' => 'two_columns' - ) - %> -<%= form_input(:hidden_field, 'contactgroup', 'id') %> -<%= form_input(:hidden_field, 'contactgroup', 'customer_id') %> - - - <%= form_input(:text_field, 'contactgroup', 'name', _('Name'), 'class'=>'two_columns') %> -
    - - - - -
    - - -
    - - <%= end_form_tag %> diff --git a/themes/olive/views/contact_groups/index.html.erb b/themes/olive/views/contact_groups/index.html.erb deleted file mode 100755 index a3dc585..0000000 --- a/themes/olive/views/contact_groups/index.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -

    <%=_('Contact Groups')%>

    - -<%- form_for @contact_group do |f| %> -<%= hidden_field "contactgroup", "user_id" %> - - - - - -<% - for contactgroup in @contactgroups %> - - - - - - -<% end %> - - - -
    <%=_('Name')%> 
    <%= contactgroup.name %><%= link_to(_('members'), :controller=>'contact', :action=>'list', :id=>contactgroup.id, :params=>{"mode"=>"groups"}) %><%= link_to(_('edit'), :controller=>'/contacts/contact_group', :action=>'edit', :id=>contactgroup.id) %><%= link_to(_('delete'), {:controller=>'/contacts/contact_group', :action=>'delete', :id=>contactgroup.id}, {:confirm=>sprintf(_('DELETE CONTACT GROUP \'%s\'?'), contactgroup.name)})%>
    - - -
    -<%- end %> diff --git a/themes/olive/views/contacts/add_multiple.html.erb b/themes/olive/views/contacts/add_multiple.html.erb deleted file mode 100755 index 8ab1494..0000000 --- a/themes/olive/views/contacts/add_multiple.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -

    <%=t :add_multiple_contacts %>

    -<% if flash["errors"] and not flash["errors"].empty?%> - <%= t(:errors)%> -
      - <% flash["errors"].each do |message| %> -
    • <%= message %> - <% end %> -
    -<% end %> -
    - <%= radio_button("contact", "file_type", "1")%> <%= t(:csv_file)%> - <%= radio_button("contact", "file_type", "2")%> <%= t(:tab_file)%> - - - - - - - - -
    - - - -
    -
    diff --git a/themes/olive/views/contacts/choose.html.erb b/themes/olive/views/contacts/choose.html.erb deleted file mode 100755 index 65fb2ac..0000000 --- a/themes/olive/views/contacts/choose.html.erb +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/themes/olive/views/contacts/import_preview.html.erb b/themes/olive/views/contacts/import_preview.html.erb deleted file mode 100755 index b972081..0000000 --- a/themes/olive/views/contacts/import_preview.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -

    <%= _('Contacts You Are About To Import')%>

    - -<% if flash["errors"] and not flash["errors"].empty?%> - <%= _('Errors')%> -
      - <% flash["errors"].each do |message| %> -
    • <%= message %> - <% end %> -
    -<% end %> - -
    - - - - - - - - - <% - for i in 0...@contacts.length - contact = @contacts[i] - %> - - - - - - - - <% end %> - - - - -
     <%= _('First name')%><%= _('Last name')%><%= _('E-mail')%>
    <%=i+1%>
    - - - - -
    -
    diff --git a/themes/olive/views/contacts/index.html.erb b/themes/olive/views/contacts/index.html.erb deleted file mode 100755 index 87e5217..0000000 --- a/themes/olive/views/contacts/index.html.erb +++ /dev/null @@ -1,115 +0,0 @@ -

    <%= t :contacts %>

    -<% unless @mode == "choose" %> - -<% end -%> -
    -
    - - <% if flash["alert"] %>
    • <%= flash["alert"] %>
    <% end %> -
    - - <% if @group_id and not @group_id.nil? %> - - <% end %> - - - - - - - - <% if @mode == "choose" %> - - - - - - <% for contact in @contacts %> - - - - - - <% end %> - - <% for group in @contactgroups %> - - - - - - <% end %> - - - - <% elsif @mode == "groups"%> - - - - - - <% for contact in @contacts %> - - - - - - - <% end %> - - - - <% else %> - - - - - - <% for contact in @contacts %> - - - - - - <% end %> - <% end %> -
    - <% CDF::CONFIG[:contact_letters].each do |letter| %> - <%= link_to letter, contacts_path(:letter => letter) %> - <% end %> -       - <%= link_to t(:show_all), contacts_path %> -
    <%= will_paginate @contacts %>
    <%= "#{t :to} #{t :cc} #{t :bcc}" %><%= t :name %><%= t :email %>
    - - <%=contact.full_name%><%=contact.email%>
    <%=t(:groups)%>:
    - - <%=group.name%> 
    - - -
    <%= t(:name)%><%= t(:email)%>
    ><%=contact.full_name%><%=contact.email%>
    - - -
    <%= t(:name)%><%= t(:email)%> 
    <%= link_to(contact.full_name, :controller=>:contacts, :action => "edit", :id => contact.id ) %><%= link_to( contact.email, :controller => :webmail, :action => "compose", :params => { "mail[to]" => contact.email } ) %><%= link_to(t(:delete), {:controller=>:contacts, :action=>'delete', :id=>contact.id}, - {:confirm=>t(:delete_contact_question, :name => contact.show_name, :email => contact.email)})%> -
    -
    -
    -
    diff --git a/themes/olive/views/contacts/new.html.erb b/themes/olive/views/contacts/new.html.erb deleted file mode 100755 index dd19e45..0000000 --- a/themes/olive/views/contacts/new.html.erb +++ /dev/null @@ -1,73 +0,0 @@ -

    <%=t(:edit_create_contact)%>

    - - -
    -
    - - - <%= form_tag( contacts_path, 'method' => 'post', 'class' => 'two_columns') do %> - <%= form_input(:hidden_field, 'contact', 'id') %> - <%= form_input(:hidden_field, 'contact', 'customer_id') %> - - - <%= form_input(:text_field, 'contact', 'fname', t(:first_name), 'class'=>'two_columns') %> - <%= form_input(:text_field, 'contact', 'lname', t(:last_name), 'class'=>'two_columns') %> - <%= form_input((@contact.new_record? ? :text_field : :read_only_field), 'contact', 'email', t(:email), 'class'=>'two_columns')%> -
    - - <% for group in @contactgroups %> - - <% end %> - <% if not(@contactgroups.empty?) %> - <%=_('Contact belong to these groups')%>: - - - <% - end - col = 1 - for group in @contactgroups %> - - <% if col%2 == 0 %> - - - <% end - col = col + 1 %> - <% end %> - <% if col%2 == 0 and not(@contactgroups.empty?) %> - - <% end %> - <% if not(@contactgroups.empty?) %> - -
    - > -  <%=group.name %> -
     
    - <% end %> - - - - - -
    - - -
    - <% end %> -
    -
    diff --git a/themes/olive/views/login/index.html.erb b/themes/olive/views/core/login.html.erb old mode 100644 new mode 100755 similarity index 76% rename from themes/olive/views/login/index.html.erb rename to themes/olive/views/core/login.html.erb index 6240f03..51b027b --- a/themes/olive/views/login/index.html.erb +++ b/themes/olive/views/core/login.html.erb @@ -5,13 +5,13 @@

    <%= t(:please_login) %>