nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you can enter "cont".
+
+
+== Console
+
+The console is a Ruby shell, which allows you to interact with your
+application's domain model. Here you'll have all parts of the application
+configured, just like it is when the application is running. You can inspect
+domain models, change values, and save to the database. Starting the script
+without arguments will launch it in the development environment.
+
+To start the console, run rails console from the application
+directory.
+
+Options:
+
+* Passing the -s, --sandbox argument will rollback any modifications
+ made to the database.
+* Passing an environment name as an argument will load the corresponding
+ environment. Example: rails console production .
+
+To reload your controllers and models after launching the console run
+reload!
+
+More information about irb can be found at:
+link:http://www.rubycentral.org/pickaxe/irb.html
+
+
+== dbconsole
+
+You can go to the command line of your database directly through rails
+dbconsole . You would be connected to the database with the credentials
+defined in database.yml. Starting the script without arguments will connect you
+to the development database. Passing an argument will connect you to a different
+database, like rails dbconsole production . Currently works for MySQL,
+PostgreSQL and SQLite 3.
+
+== Description of Contents
+
+The default directory structure of a generated Ruby on Rails application:
+
+ |-- app
+ | |-- assets
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
+ | |-- controllers
+ | |-- helpers
+ | |-- mailers
+ | |-- models
+ | `-- views
+ | `-- layouts
+ |-- config
+ | |-- environments
+ | |-- initializers
+ | `-- locales
+ |-- db
+ |-- doc
+ |-- lib
+ | `-- tasks
+ |-- log
+ |-- public
+ |-- script
+ |-- test
+ | |-- fixtures
+ | |-- functional
+ | |-- integration
+ | |-- performance
+ | `-- unit
+ |-- tmp
+ | |-- cache
+ | |-- pids
+ | |-- sessions
+ | `-- sockets
+ `-- vendor
+ |-- assets
+ `-- stylesheets
+ `-- plugins
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/assets
+ Contains subdirectories for images, stylesheets, and JavaScript files.
+
+app/controllers
+ Holds controllers that should be named like weblogs_controller.rb for
+ automated URL mapping. All controllers should descend from
+ ApplicationController which itself descends from ActionController::Base.
+
+app/models
+ Holds models that should be named like post.rb. Models descend from
+ ActiveRecord::Base by default.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
+ eRuby syntax by default.
+
+app/views/layouts
+ Holds the template files for layouts to be used with views. This models the
+ common header/footer method of wrapping views. In your views, define a layout
+ using the layout :default and create a file named default.html.erb.
+ Inside default.html.erb, call <% yield %> to render the view using this
+ layout.
+
+app/helpers
+ Holds view helpers that should be named like weblogs_helper.rb. These are
+ generated for you automatically when using generators for controllers.
+ Helpers can be used to wrap functionality for your views into methods.
+
+config
+ Configuration files for the Rails environment, the routing map, the database,
+ and other dependencies.
+
+db
+ Contains the database schema in schema.rb. db/migrate contains all the
+ sequence of Migrations for your schema.
+
+doc
+ This directory is where your application documentation will be stored when
+ generated using rake doc:app
+
+lib
+ Application specific libraries. Basically, any kind of custom code that
+ doesn't belong under controllers, models, or helpers. This directory is in
+ the load path.
+
+public
+ The directory available for the web server. Also contains the dispatchers and the
+ default HTML files. This should be set as the DOCUMENT_ROOT of your web
+ server.
+
+script
+ Helper scripts for automation and generation.
+
+test
+ Unit and functional tests along with fixtures. When using the rails generate
+ command, template test files will be generated for you and placed in this
+ directory.
+
+vendor
+ External libraries that the application depends on. Also includes the plugins
+ subdirectory. If the app has frozen rails, those gems also go here, under
+ vendor/rails/. This directory is in the load path.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 00000000..f29eb6f1
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,7 @@
+#!/usr/bin/env rake
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+
+Osem::Application.load_tasks
diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png
new file mode 100644
index 00000000..d5edc04e
Binary files /dev/null and b/app/assets/images/rails.png differ
diff --git a/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png b/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 00000000..5b5dab2a
Binary files /dev/null and b/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/app/assets/images/ui-bg_flat_55_fbec88_40x100.png b/app/assets/images/ui-bg_flat_55_fbec88_40x100.png
new file mode 100644
index 00000000..47acaadd
Binary files /dev/null and b/app/assets/images/ui-bg_flat_55_fbec88_40x100.png differ
diff --git a/app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png b/app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png
new file mode 100644
index 00000000..9fb564f8
Binary files /dev/null and b/app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png differ
diff --git a/app/assets/images/ui-bg_glass_85_dfeffc_1x400.png b/app/assets/images/ui-bg_glass_85_dfeffc_1x400.png
new file mode 100644
index 00000000..01495152
Binary files /dev/null and b/app/assets/images/ui-bg_glass_85_dfeffc_1x400.png differ
diff --git a/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png b/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 00000000..8f28bc99
Binary files /dev/null and b/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
new file mode 100644
index 00000000..81ecc362
Binary files /dev/null and b/app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png differ
diff --git a/app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png b/app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png
new file mode 100644
index 00000000..4f3faf8a
Binary files /dev/null and b/app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png differ
diff --git a/app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
new file mode 100644
index 00000000..38c38335
Binary files /dev/null and b/app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png differ
diff --git a/app/assets/images/ui-icons_217bc0_256x240.png b/app/assets/images/ui-icons_217bc0_256x240.png
new file mode 100644
index 00000000..6f4bd87c
Binary files /dev/null and b/app/assets/images/ui-icons_217bc0_256x240.png differ
diff --git a/app/assets/images/ui-icons_2e83ff_256x240.png b/app/assets/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 00000000..09d1cdc8
Binary files /dev/null and b/app/assets/images/ui-icons_2e83ff_256x240.png differ
diff --git a/app/assets/images/ui-icons_469bdd_256x240.png b/app/assets/images/ui-icons_469bdd_256x240.png
new file mode 100644
index 00000000..bd2cf079
Binary files /dev/null and b/app/assets/images/ui-icons_469bdd_256x240.png differ
diff --git a/app/assets/images/ui-icons_6da8d5_256x240.png b/app/assets/images/ui-icons_6da8d5_256x240.png
new file mode 100644
index 00000000..9f3eafaa
Binary files /dev/null and b/app/assets/images/ui-icons_6da8d5_256x240.png differ
diff --git a/app/assets/images/ui-icons_cd0a0a_256x240.png b/app/assets/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 00000000..2ab019b7
Binary files /dev/null and b/app/assets/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/app/assets/images/ui-icons_d8e7f3_256x240.png b/app/assets/images/ui-icons_d8e7f3_256x240.png
new file mode 100644
index 00000000..ad2dc6f9
Binary files /dev/null and b/app/assets/images/ui-icons_d8e7f3_256x240.png differ
diff --git a/app/assets/images/ui-icons_f9bd01_256x240.png b/app/assets/images/ui-icons_f9bd01_256x240.png
new file mode 100644
index 00000000..c7c53cb1
Binary files /dev/null and b/app/assets/images/ui-icons_f9bd01_256x240.png differ
diff --git a/app/assets/javascripts/admin/admin.js.coffee b/app/assets/javascripts/admin/admin.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/admin.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/admin/call_for_papers.js.coffee b/app/assets/javascripts/admin/call_for_papers.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/call_for_papers.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/admin/conference.js.coffee b/app/assets/javascripts/admin/conference.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/conference.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/admin/people.js.coffee b/app/assets/javascripts/admin/people.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/people.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/admin/users.js.coffee b/app/assets/javascripts/admin/users.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/users.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/admin/venue.js.coffee b/app/assets/javascripts/admin/venue.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/admin/venue.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 00000000..e5ec8a6e
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,74 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
+// GO AFTER THE REQUIRES BELOW.
+//
+//= require jquery
+//= require jquery_ujs
+//= require jquery-ui
+//= require jquery-fileupload
+//= require twitter/bootstrap
+//= require cocoon
+
+$(function() {
+
+ $('.dropdown-toggle').dropdown();
+ $("#conference-start-datepicker").datepicker({
+ dateFormat: 'yy/mm/dd',
+ numberOfMonths: 2,
+ onSelect: function(selected) {
+ $("#conference-end-datepicker").datepicker("option","minDate", selected)
+ }
+ });
+ $("#conference-end-datepicker").datepicker({
+ dateFormat: 'yy/mm/dd',
+ numberOfMonths: 2,
+ onSelect: function(selected) {
+ $("#conference-start-datepicker").datepicker("option","maxDate", selected)
+ $("#cfp-hard-datepicker").datepicker("option","minDate", selected)
+
+ }
+ });
+
+ $("#cfp-hard-datepicker").datepicker({
+ dateFormat: 'yy/mm/dd',
+ numberOfMonths: 2,
+ onSelect: function(selected) {
+ $("#conference-end-datepicker").datepicker("option","maxDate", selected)
+ $("#conference-start-datepicker").datepicker("option","maxDate", selected)
+
+ }
+ });
+
+ $(".comment-reply-link").click(function(){
+ $(".comment-reply", $(this).parent()).toggle();
+ return false;
+ });
+
+ $("#event-comment-link").click(function(){
+ $("#comments-div").toggle();
+ return false;
+ });
+
+ $(".comment-reply").hide();
+ $(".user-details-popover").popover();
+ $("#comments-div").hide();
+});
+
+function word_count(text, divId){
+ var r = 0;
+ var input = text.value.replace(/\s/g,' ');
+ var word_array = input.split(' ');
+ for (var i=0; i < word_array.length; i++) {
+ if (word_array[i].length > 0) r++;
+ }
+
+ $('#' + divId).text(r);
+}
\ No newline at end of file
diff --git a/app/assets/javascripts/bootstrap.js.coffee b/app/assets/javascripts/bootstrap.js.coffee
new file mode 100644
index 00000000..c9404a8e
--- /dev/null
+++ b/app/assets/javascripts/bootstrap.js.coffee
@@ -0,0 +1,4 @@
+jQuery ->
+ $("a[rel=popover]").popover()
+ $(".tooltip").tooltip()
+ $("a[rel=tooltip]").tooltip()
\ No newline at end of file
diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/home.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/stylesheets/admin/admin.css.scss b/app/assets/stylesheets/admin/admin.css.scss
new file mode 100644
index 00000000..914a004c
--- /dev/null
+++ b/app/assets/stylesheets/admin/admin.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/admin controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/call_for_papers.css.scss b/app/assets/stylesheets/admin/call_for_papers.css.scss
new file mode 100644
index 00000000..80170c0a
--- /dev/null
+++ b/app/assets/stylesheets/admin/call_for_papers.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/call_for_papers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/conference.css.scss b/app/assets/stylesheets/admin/conference.css.scss
new file mode 100644
index 00000000..82491508
--- /dev/null
+++ b/app/assets/stylesheets/admin/conference.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/conference controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/people.css.scss b/app/assets/stylesheets/admin/people.css.scss
new file mode 100644
index 00000000..de8637ff
--- /dev/null
+++ b/app/assets/stylesheets/admin/people.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/people controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/users.css.scss b/app/assets/stylesheets/admin/users.css.scss
new file mode 100644
index 00000000..925e3122
--- /dev/null
+++ b/app/assets/stylesheets/admin/users.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/users controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/admin/venue.css.scss b/app/assets/stylesheets/admin/venue.css.scss
new file mode 100644
index 00000000..6a29a24d
--- /dev/null
+++ b/app/assets/stylesheets/admin/venue.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin/venue controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
new file mode 100644
index 00000000..95b82e02
--- /dev/null
+++ b/app/assets/stylesheets/application.css
@@ -0,0 +1,7 @@
+/*
+ *= require formtastic-bootstrap
+ *= require bootstrap_and_overrides
+ *= require osem
+ *= require jquery-ui-1.9.2.custom
+ *= require jquery.fileupload-ui
+*/
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css b/app/assets/stylesheets/bootstrap_and_overrides.css
new file mode 100644
index 00000000..131fcfd7
--- /dev/null
+++ b/app/assets/stylesheets/bootstrap_and_overrides.css
@@ -0,0 +1,7 @@
+/*
+ =require twitter-bootstrap-static/bootstrap
+
+ Use Font Awesome icons (default)
+ To use Glyphicons sprites instead of Font Awesome, replace with "require twitter-bootstrap-static/sprites"
+ =require twitter-bootstrap-static/fontawesome
+ */
\ No newline at end of file
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less
new file mode 100644
index 00000000..67299b01
--- /dev/null
+++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less
@@ -0,0 +1,33 @@
+@import "twitter/bootstrap/bootstrap";
+@import "twitter/bootstrap/responsive";
+
+// Set the correct sprite paths
+@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings");
+@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white");
+
+// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
+// Note: If you use asset_path() here, your compiled bootstrap_and_overrides.css will not
+// have the proper paths. So for now we use the absolute path.
+@fontAwesomeEotPath: asset-path("fontawesome-webfont.eot");
+@fontAwesomeEotPath_iefix: asset-path("fontawesome-webfont.eot#iefix");
+@fontAwesomeWoffPath: asset-path("fontawesome-webfont.woff");
+@fontAwesomeTtfPath: asset-path("fontawesome-webfont.ttf");
+@fontAwesomeSvgPath: asset-path("fontawesome-webfont.svg");
+
+// Font Awesome
+@import "fontawesome";
+
+// Glyphicons
+//@import "twitter/bootstrap/sprites.less";
+
+// Your custom LESS stylesheets goes here
+//
+// Since bootstrap was imported above you have access to its mixins which
+// you may use and inherit here
+//
+// If you'd like to override bootstrap's own variables, you can do so here as well
+// See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation
+//
+// Example:
+// @linkColor: #ff0000;
+@navbarBackground: #000000;
\ No newline at end of file
diff --git a/app/assets/stylesheets/home.css.scss b/app/assets/stylesheets/home.css.scss
new file mode 100644
index 00000000..f0ddc684
--- /dev/null
+++ b/app/assets/stylesheets/home.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the home controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/jquery-ui-1.9.2.custom.css b/app/assets/stylesheets/jquery-ui-1.9.2.custom.css
new file mode 100644
index 00000000..0234adb1
--- /dev/null
+++ b/app/assets/stylesheets/jquery-ui-1.9.2.custom.css
@@ -0,0 +1,325 @@
+/*! jQuery UI - v1.9.2 - 2012-12-28
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.datepicker.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande%2CLucida%20Sans%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; }
+.ui-state-hover a, .ui-state-hover a:hover, .ui-state-hover a:link, .ui-state-hover a:visited { color: #1d5987; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-state-disabled .ui-icon { filter:Alpha(Opacity=35); } /* For IE8 - See #6059 */
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -khtml-border-top-left-radius: 5px; border-top-left-radius: 5px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -khtml-border-top-right-radius: 5px; border-top-right-radius: 5px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -khtml-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; -khtml-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
\ No newline at end of file
diff --git a/app/assets/stylesheets/osem.css b/app/assets/stylesheets/osem.css
new file mode 100644
index 00000000..56bb4479
--- /dev/null
+++ b/app/assets/stylesheets/osem.css
@@ -0,0 +1,102 @@
+html, body,.container,.content {
+ height: 100%;
+}
+
+#push,
+#footer {
+ height: 60px;
+}
+
+.container, .content {
+ position: relative;
+}
+#inner-content {
+ padding-top: 60px;
+}
+
+#wrap {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -60px;
+}
+
+#footer {
+ position: relative;
+ background-color: #ffffff;
+}
+.navbar-inner {
+ background: #299a0b; /* Old browsers */
+ background: -moz-linear-gradient(top, #299a0b 0%, #299a0b 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#299a0b), color-stop(100%,#299a0b)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #299a0b 0%,#299a0b 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #299a0b 0%,#299a0b 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #299a0b 0%,#299a0b 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #299a0b 0%,#299a0b 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#299a0b', endColorstr='#299a0b',GradientType=0 ); /* IE6-9 */
+}
+
+.navbar .nav .active > a, .navbar .nav .active > a:hover {
+ background-color: #DFE0DF;
+}
+
+.navbar .divider-vertical {
+ background-color: #b4e391;
+ border-right: 1px solid #eab92d;
+}
+
+.navbar-fixed-top .brand {
+ color: #ffffff;
+ text-shadow:none;
+}
+
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 15px 10px;
+ color: white;
+ text-decoration: none;
+ text-shadow: none;
+}
+
+.center {
+ margin:0 auto;
+ float:none;
+}
+
+.center-text {
+ text-align: center
+}
+
+textarea {
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
+ box-sizing: border-box; /* Opera/IE 8+ */
+ width:100%;
+}
+
+.mainbutton {
+ background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #9dce2c), color-stop(1, #8cb82b) );
+ background:-moz-linear-gradient( center top, #9dce2c 5%, #8cb82b 100% );
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9dce2c', endColorstr='#8cb82b');
+ background-color:#9dce2c;
+ -moz-border-radius:6px;
+ -webkit-border-radius:6px;
+ border-radius:6px;
+ border:1px solid #83c41a;
+ display:inline-block;
+ color:#ffffff;
+ font-family:Arial;
+ font-size:20px;
+ font-weight:bold;
+ padding:6px 24px;
+ text-decoration:none;
+ text-shadow:1px 1px 9px #689324;
+}.mainbutton:hover {
+ background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #8cb82b), color-stop(1, #9dce2c) );
+ background:-moz-linear-gradient( center top, #8cb82b 5%, #9dce2c 100% );
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#8cb82b', endColorstr='#9dce2c');
+ background-color:#8cb82b;
+ }.mainbutton:active {
+ position:relative;
+ top:1px;
+ }
diff --git a/app/controllers/admin/callforpapers_controller.rb b/app/controllers/admin/callforpapers_controller.rb
new file mode 100644
index 00000000..c4534af7
--- /dev/null
+++ b/app/controllers/admin/callforpapers_controller.rb
@@ -0,0 +1,28 @@
+class Admin::CallforpapersController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def show
+ @cfp = @conference.call_for_papers
+ if @cfp.nil?
+ @cfp = CallForPapers.new
+ end
+ end
+
+ def update
+ @cfp = @conference.call_for_papers
+ @cfp.update_attributes(params[:call_for_papers])
+ redirect_to(admin_conference_cfp_info_path(:id => @conference.short_title), :notice => 'Call for Papers was successfully updated.')
+
+ end
+
+ def create
+ @cfp = CallForPapers.new(params[:call_for_papers])
+ @conference.call_for_papers = @cfp
+ if @cfp.save
+ redirect_to(admin_conference_cfp_info_path(:id => @conference.short_title), :notice => 'Call for Papers was successfully updated.')
+ else
+ redirect_to(admin_conference_cfp_info_path(:id => @conference.short_title), :error => 'Call for Papers failed.')
+ end
+ end
+end
diff --git a/app/controllers/admin/conference_controller.rb b/app/controllers/admin/conference_controller.rb
new file mode 100644
index 00000000..763e9b7d
--- /dev/null
+++ b/app/controllers/admin/conference_controller.rb
@@ -0,0 +1,36 @@
+class Admin::ConferenceController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def index
+ @conferences = Conference.all
+ if @conferences.count == 0
+ redirect_to new_admin_conference_path
+ return
+ end
+ end
+
+ def new
+ @conference = Conference.new
+ end
+
+ def create
+ @conference = Conference.new(params[:conference])
+ if @conference.save
+ redirect_to(admin_conference_path(:id => @conference.short_title), :notice => 'Conference was successfully created.')
+ else
+ render :action => "new"
+ end
+ end
+
+ def update
+ @conference = Conference.find_all_by_short_title(params[:id]).first
+ @conference.update_attributes(params[:conference])
+ flash[:notice] = "Updated Conference"
+ redirect_to(admin_conference_path(:id => @conference.short_title), :notice => 'Conference was successfully updated.')
+ end
+
+ def show
+ @conference = Conference.find_all_by_short_title(params[:id]).first
+ end
+end
diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb
new file mode 100644
index 00000000..764b752a
--- /dev/null
+++ b/app/controllers/admin/events_controller.rb
@@ -0,0 +1,41 @@
+class Admin::EventsController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def index
+ @events = @conference.events
+ end
+
+ def show
+ @event = @conference.events.find(params[:id])
+ @comments = @event.root_comments
+ @comment_count = @event.comment_threads.count
+ end
+
+ def edit
+ @event_types = @conference.event_types
+ @event = Event.find_by_id(params[:id])
+ @person = @event.submitter
+ @url = edit_admin_conference_event_path(@conference.short_title, @event)
+ end
+
+ def comment
+ event = @conference.events.find_by_id(params[:id])
+ comment = Comment.build_from(event, current_user.id, params[:comment])
+ comment.save!
+ if !params[:parent].nil?
+ comment.move_to_child_of(params[:parent])
+ end
+
+ redirect_to admin_conference_event_path(:conference_id => @conference.short_title, :id => params[:id])
+ end
+ def create
+
+ end
+
+ def update_state
+ event = Event.find(params[:id])
+ event.send(:"#{params[:transition]}!", :send_mail => params[:send_mail])
+ redirect_to(admin_conference_events_path(:conference_id => @conference.short_title), :notice => "Updated state")
+ end
+end
diff --git a/app/controllers/admin/eventtype_controller.rb b/app/controllers/admin/eventtype_controller.rb
new file mode 100644
index 00000000..64aa3939
--- /dev/null
+++ b/app/controllers/admin/eventtype_controller.rb
@@ -0,0 +1,17 @@
+class Admin::EventtypeController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def show
+ render :event_type_list
+ end
+
+ def update
+ begin
+ @conference.update_attributes!(params[:conference])
+ redirect_to(admin_conference_eventtype_list_path(:conference_id => @conference.short_title), :notice => 'Event types were successfully updated.')
+ rescue Exception => e
+ redirect_to(admin_conference_eventtype_list_path(:conference_id => @conference.short_title), :alert => 'Event types update failed: #{e.message}')
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/admin/people_controller.rb b/app/controllers/admin/people_controller.rb
new file mode 100644
index 00000000..d31f4c2d
--- /dev/null
+++ b/app/controllers/admin/people_controller.rb
@@ -0,0 +1,24 @@
+class Admin::PeopleController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def index
+ @people = Person.all
+ end
+
+ def create
+
+ end
+
+ def show
+ @person = Person.find(params[:id])
+ end
+
+ def update
+
+ end
+
+ def delete
+
+ end
+end
diff --git a/app/controllers/admin/registrations_controller.rb b/app/controllers/admin/registrations_controller.rb
new file mode 100644
index 00000000..ee92e8c1
--- /dev/null
+++ b/app/controllers/admin/registrations_controller.rb
@@ -0,0 +1,16 @@
+class Admin::RegistrationsController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def show
+ session[:return_to] ||= request.referer
+ @pdf_filename = "#{@conference.title}.pdf"
+ @registrations = @conference.registrations.all(:joins => :person,
+ :order => "people.last_name ASC",
+ :select => "registrations.*,
+ people.last_name AS last_name,
+ people.first_name AS first_name,
+ people.public_name AS public_name,
+ people.email AS email")
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/admin/rooms_controller.rb b/app/controllers/admin/rooms_controller.rb
new file mode 100644
index 00000000..b736b22f
--- /dev/null
+++ b/app/controllers/admin/rooms_controller.rb
@@ -0,0 +1,18 @@
+class Admin::RoomsController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def show
+ render :rooms_list
+ end
+
+ def update
+ if @conference.update_attributes(params[:conference])
+ redirect_to(admin_conference_rooms_list_path(:conference_id => @conference.short_title), :notice => 'Rooms were successfully updated.')
+ else
+ redirect_to(admin_conference_rooms_list_path(:conference_id => @conference.short_title), :notice => 'Room update failed.')
+ end
+
+ end
+
+end
diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb
new file mode 100644
index 00000000..88798893
--- /dev/null
+++ b/app/controllers/admin/tracks_controller.rb
@@ -0,0 +1,18 @@
+class Admin::TracksController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def show
+ render :tracks_list
+ end
+
+ def update
+ if @conference.update_attributes(params[:conference])
+ redirect_to(admin_conference_tracks_list_path(:conference_id => @conference.short_title), :notice => 'Tracks were successfully updated.')
+ else
+ redirect_to(admin_conference_tracks_list_path(:conference_id => @conference.short_title), :notice => 'Tracks update failed.')
+ end
+
+ end
+
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
new file mode 100644
index 00000000..35ab58d4
--- /dev/null
+++ b/app/controllers/admin/users_controller.rb
@@ -0,0 +1,20 @@
+class Admin::UsersController < ApplicationController
+ before_filter :verify_admin
+ layout "admin"
+
+ def index
+ @users = User.all(:joins => :person,
+ :order => "people.last_name ASC",
+ :select => "users.*,
+ people.last_name AS last_name,
+ people.first_name AS first_name,
+ people.public_name AS public_name,
+ people.email AS email")
+ end
+
+ def update
+ user = User.find(params[:id])
+ user.update_attributes!(params[:user])
+ redirect_to admin_users_path, :notice => "Updated #{user.email}"
+ end
+end
diff --git a/app/controllers/admin/venue_controller.rb b/app/controllers/admin/venue_controller.rb
new file mode 100644
index 00000000..d8ad1c5d
--- /dev/null
+++ b/app/controllers/admin/venue_controller.rb
@@ -0,0 +1,24 @@
+class Admin::VenueController < ApplicationController
+ before_filter :verify_organizer
+ layout "admin"
+
+ def index
+ end
+
+ def update
+ @venue = @conference.venue
+ venue_params = params[:venue]
+ @venue.name = venue_params[:name]
+ @venue.address= venue_params[:address]
+ @venue.website = venue_params[:website]
+ @venue.description = venue_params[:description]
+ @venue.save
+ redirect_to(admin_conference_venue_info_path(:conference_id => @conference.short_title), :notice => 'Venue was successfully updated.')
+ end
+
+ def show
+ @venue = @conference.venue
+ render :venue_info
+ end
+
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 00000000..d45387fa
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,42 @@
+class ApplicationController < ActionController::Base
+ include ApplicationHelper
+
+ protect_from_forgery
+
+ def verify_user
+ :authenticate_user!
+
+ if (current_user.nil?)
+ redirect_to '/accounts/sign_in'
+ return
+ end
+
+ @conference = Conference.find_all_by_short_title(params[:conference_id]).first
+ end
+
+ def organizer_or_admin?
+ has_role?(current_user, 'admin') || has_role?(current_user, 'organizer')
+ end
+
+ def verify_organizer
+ verify_user
+
+ ## Todo simplify this
+ redirect_to '/' unless has_role?(current_user, 'admin') || has_role?(current_user, 'organizer')
+ end
+
+ def verify_admin
+ verify_user
+
+ redirect_to '/' unless has_role?(current_user, 'admin')
+ end
+
+ def current_ability
+ @current_ability ||= AdminAbility.new(current_user)
+ end
+
+ rescue_from CanCan::AccessDenied do |exception|
+ Rails.logger.debug("Access denied!")
+ redirect_to '/', :alert => exception.message
+ end
+end
diff --git a/app/controllers/conference_registration_controller.rb b/app/controllers/conference_registration_controller.rb
new file mode 100644
index 00000000..50b8d3d5
--- /dev/null
+++ b/app/controllers/conference_registration_controller.rb
@@ -0,0 +1,51 @@
+class ConferenceRegistrationController < ApplicationController
+ before_filter :verify_user
+
+ def register
+ # TODO Figure out how to change the route's id from :id to :conference_id
+ @conference = Conference.find_all_by_short_title(params[:id]).first
+ @person = current_user.person
+ @registration = @person.registrations.where(:conference_id => @conference.id).first
+ @registered = true
+ if @registration.nil?
+ @registered = false
+ @registration = @person.registrations.new(:conference_id => @conference.id)
+ end
+ end
+
+ def update
+ conference = Conference.find_all_by_short_title(params[:id]).first
+ person = current_user.person
+ registration = person.registrations.where(:conference_id => conference.id).first
+ update_registration = true
+ begin
+ if registration.nil?
+ update_registration = false
+ person.update_attributes(params[:registration][:person_attributes])
+ params[:registration].delete :person_attributes
+ registration = person.registrations.new(params[:registration])
+ registration.conference_id = conference.id
+ registration.save!
+ else
+ registration.update_attributes!(params[:registration])
+ end
+ rescue Exception => e
+ redirect_to(register_conference_path(:id => conference.short_title), :alert => 'Registration failed:' + e.message)
+ return
+ end
+
+ redirect_message = "You are now registered."
+ if update_registration
+ redirect_message = "Registration updated."
+ end
+ redirect_to(register_conference_path(:id => conference.short_title), :notice => redirect_message)
+ end
+
+ def unregister
+ conference = Conference.find_all_by_short_title(params[:id]).first
+ person = current_user.person
+ registration = person.registrations.where(:conference_id => conference.id).first
+ registration.destroy
+ redirect_to :root
+ end
+end
diff --git a/app/controllers/event_attachments_controller.rb b/app/controllers/event_attachments_controller.rb
new file mode 100644
index 00000000..95248f0c
--- /dev/null
+++ b/app/controllers/event_attachments_controller.rb
@@ -0,0 +1,97 @@
+class EventAttachmentsController < ApplicationController
+ before_filter :verify_user
+ skip_before_filter :verify_user, :only => [:show]
+
+ def index
+ @proposal = current_user.person.events.find(params[:proposal_id])
+ @uploads = @proposal.event_attachments
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @uploads.map{|upload| upload.to_jq_upload } }
+ end
+ end
+
+ def show
+ upload = EventAttachment.find(params[:id])
+ if upload.public?
+ send_file upload.attachment.path
+ return
+ end
+
+ if current_user.nil?
+ verify_user
+ return
+ end
+
+ if organizer_or_admin? || current_user.person == upload.event.submitter
+ send_file upload.attachment.path
+ else
+ raise ActionController::RoutingError.new('Not Found')
+ end
+ end
+
+ def new
+ @upload = EventAttachment.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render json: @upload }
+ end
+ end
+
+ def edit
+ @upload = EventAttachment.find(params[:id])
+ end
+
+ def create
+ params[:event_attachment][:title] = params[:title][0]
+ params[:event_attachment][:public] = false
+ params[:event_attachment][:event_id] = params[:proposal_id]
+
+ if params.has_key?(:public)
+ params[:event_attachment][:public] = true
+ end
+ @upload = EventAttachment.new(params[:event_attachment])
+
+ respond_to do |format|
+ if @upload.save
+ format.html {
+ render :json => [@upload.to_jq_upload].to_json,
+ :content_type => 'text/html',
+ :layout => false
+ }
+ format.json { render json: [@upload.to_jq_upload].to_json, status: :created,
+ location: conference_proposal_event_attachment_path(@upload.event.conference.short_title, @upload.event, @upload) }
+ else
+ format.html { render action: "new" }
+ format.json { render json: @upload.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ @upload = EventAttachment.find(params[:proposal_id])
+
+ respond_to do |format|
+ if @upload.update_attributes(params[:upload])
+ format.html { redirect_to @upload, notice: 'Upload was successfully updated.' }
+ format.json { head :no_content }
+ else
+ format.html { render action: "edit" }
+ format.json { render json: @upload.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ @proposal = current_user.person.events.find(params[:proposal_id])
+ @upload = @proposal.event_attachments.find(params[:id])
+ @upload.destroy
+
+ respond_to do |format|
+ format.html { redirect_to uploads_url }
+ format.json { head :no_content }
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 00000000..089c8d49
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,7 @@
+class HomeController < ApplicationController
+ def index
+ @today = Date.current
+ @conferences = Conference.where("start_date >= ?", @today)
+ end
+
+end
diff --git a/app/controllers/proposal_controller.rb b/app/controllers/proposal_controller.rb
new file mode 100644
index 00000000..15bb60a4
--- /dev/null
+++ b/app/controllers/proposal_controller.rb
@@ -0,0 +1,97 @@
+class ProposalController < ApplicationController
+ before_filter :verify_user
+
+ def index
+ @person = current_user.person
+ @events = @person.proposals @conference
+ end
+
+ def destroy
+ current_user.person.withdraw_proposal(params[:id])
+ redirect_to(conference_proposal_index_path(:conference_id => @conference.short_title), :alert => 'Proposal withdrawn.')
+ end
+
+ def new
+ @url = conference_proposal_index_path(@conference.short_title)
+ @event = Event.new
+ @event_types = @conference.event_types
+ @person = current_user.person
+ end
+
+ def edit
+ @url = conference_proposal_path(@conference.short_title, params[:id])
+ @person = current_user.person
+ @event_types = @conference.event_types
+ @event = @person.events.find_by_id(params[:id])
+ @attachments = @event.event_attachments
+
+ if @event.nil?
+ redirect_to(conference_proposal_index_path(:conference_id => @conference.short_title), :alert => 'Invalid proposal.')
+ end
+ end
+
+ def update
+ person = current_user.person
+ session[:return_to] ||= request.referer
+ submitter = params[:person]
+
+ params[:event].delete :people_attributes
+ params[:event].delete :person
+
+ if submitter[:public_name] != person.public_name || submitter[:biography] != person.biography
+ person.update_attributes(submitter)
+ end
+
+ event = Event.find_by_id(params[:id])
+
+ if event.update_attributes!(params[:event])
+ redirect_to(conference_proposal_index_path(:conference_id => @conference.short_title), :notice => "'#{event.title}' was successfully updated.")
+ else
+ redirect_to session[:return_to], :alert => "'#{event.title}' was NOT updated!"
+ end
+ end
+
+ def create
+ person = current_user.person
+ session[:return_to] ||= request.referer
+
+ event_params = params[:event]
+ submitter = params[:person]
+ params[:event].delete :person
+
+ # First, update the submitter's info, if they've changed anything
+ if submitter[:public_name] != person.public_name || submitter[:biography] != person.biography
+ person.update_attributes(submitter)
+ end
+
+ @event = Event.new(event_params)
+ @event.conference = @conference
+ @event.event_people.new(:person => person,
+ :event_role => "submitter")
+ @event.event_people.new(:person => person,
+ :event_role => "speaker")
+
+ begin
+ @event.save!
+ rescue Exception => e
+ @url = conference_proposal_index_path(@conference.short_title)
+ @event_types = @conference.event_types
+ @person = current_user.person
+
+ flash[:error] = "Could not submit proposal: #{e.message}"
+ render :action => 'new'
+ return
+ end
+
+ registration = person.registrations.where(:conference_id => @conference.id).first
+ if registration.nil?
+ redirect_to(register_conference_path(@conference.short_title), :notice => 'Event was successfully submitted. You probably want to register for the conference now!')
+ else
+ redirect_to(conference_proposal_index_path(:conference_id => @conference.short_title), :notice => 'Event was successfully submitted.')
+ end
+ end
+
+ def show
+
+ end
+end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
new file mode 100644
index 00000000..4d97851a
--- /dev/null
+++ b/app/controllers/registrations_controller.rb
@@ -0,0 +1,52 @@
+class RegistrationsController < Devise::RegistrationsController
+ def update
+ @user = User.find(current_user.id)
+ email_changed = false
+
+ if !params[:user][:email].nil?
+ if @user.email != params[:user][:email]
+ email_changed = true
+ else
+ params[:user].delete :email
+ end
+ end
+
+ password_changed = false
+ if !params[:user][:password].nil?
+ if !params[:user][:password].empty?
+ password_changed = true
+ else
+ params[:user].delete :password
+ params[:user].delete :password_confirmation
+ params[:user].delete :current_password
+ end
+ end
+
+
+ successfully_updated = if email_changed or password_changed
+ @user.update_with_password(params[:user])
+ else
+ @user.update_without_password(params[:user])
+ end
+
+ if successfully_updated
+ set_flash_message :notice, :updated
+ # Sign in the user bypassing validation in case his password changed
+ sign_in @user, :bypass => true
+ redirect_to after_update_path_for(@user)
+ else
+ render "edit"
+ end
+ end
+
+ protected
+
+ def after_update_path_for(resource)
+ edit_user_registration_path(resource)
+ end
+
+ def after_sign_up_path_for(resource)
+ edit_user_registration_path(resource)
+ end
+
+end
\ No newline at end of file
diff --git a/app/helpers/admin/call_for_papers_helper.rb b/app/helpers/admin/call_for_papers_helper.rb
new file mode 100644
index 00000000..6e8ba54b
--- /dev/null
+++ b/app/helpers/admin/call_for_papers_helper.rb
@@ -0,0 +1,2 @@
+module Admin::CallForPapersHelper
+end
diff --git a/app/helpers/admin/conference_helper.rb b/app/helpers/admin/conference_helper.rb
new file mode 100644
index 00000000..c17678c0
--- /dev/null
+++ b/app/helpers/admin/conference_helper.rb
@@ -0,0 +1,2 @@
+module Admin::ConferenceHelper
+end
diff --git a/app/helpers/admin/people_helper.rb b/app/helpers/admin/people_helper.rb
new file mode 100644
index 00000000..a6e7e7fa
--- /dev/null
+++ b/app/helpers/admin/people_helper.rb
@@ -0,0 +1,2 @@
+module Admin::PeopleHelper
+end
diff --git a/app/helpers/admin/users_helper.rb b/app/helpers/admin/users_helper.rb
new file mode 100644
index 00000000..5995c2aa
--- /dev/null
+++ b/app/helpers/admin/users_helper.rb
@@ -0,0 +1,2 @@
+module Admin::UsersHelper
+end
diff --git a/app/helpers/admin/venue_helper.rb b/app/helpers/admin/venue_helper.rb
new file mode 100644
index 00000000..409948c7
--- /dev/null
+++ b/app/helpers/admin/venue_helper.rb
@@ -0,0 +1,2 @@
+module Admin::VenueHelper
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 00000000..ed736e54
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,43 @@
+module ApplicationHelper
+
+ def add_association_link(association_name, form_builder, div_class, html_options = {})
+ link_to_add_association "Add " + association_name.to_s.singularize, form_builder, div_class, html_options.merge(:class => "assoc btn btn-success")
+ end
+
+ def remove_association_link(association_name, form_builder)
+ link_to_remove_association("Remove " + association_name.to_s.singularize, form_builder, :class => "assoc btn btn-danger") + tag(:hr)
+ end
+
+ def dynamic_association(association_name, title, form_builder, options = {})
+ render "shared/dynamic_association", :association_name => association_name, :title => title, :f => form_builder, :hint => options[:hint]
+ end
+
+ def has_role?(current_user, role)
+ return !!current_user.role?(role.to_s.camelize)
+ end
+
+ # TODO Output better html
+ def format_comments(comment, padding = 0)
+ result = ""
+ result += ""
+ result += "
"
+ result += "
#{comment.user.person.public_name} #{comment.created_at} "
+ result += comment.body
+ result += "
"
+ result += "
"
+ result += "
"
+ #result += edit_admin_conference_event_path(@conference.short_title, @event)
+ comment.children.each do |child|
+ result += format_comments(child, 50)
+ result += "
"
+ end
+
+ result
+ end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
new file mode 100644
index 00000000..23de56ac
--- /dev/null
+++ b/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/models/.gitkeep b/app/models/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/models/ability.rb b/app/models/ability.rb
new file mode 100644
index 00000000..58929d7a
--- /dev/null
+++ b/app/models/ability.rb
@@ -0,0 +1,28 @@
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ # Define abilities for the passed in user here. For example:
+ #
+ # user ||= User.new # guest user (not logged in)
+ # if user.admin?
+ # can :manage, :all
+ # else
+ # can :read, :all
+ # end
+ #
+ # The first argument to `can` is the action you are giving the user permission to do.
+ # If you pass :manage it will apply to every action. Other common actions here are
+ # :read, :create, :update and :destroy.
+ #
+ # The second argument is the resource the user can perform the action on. If you pass
+ # :all it will apply to every resource. Otherwise pass a Ruby class of the resource.
+ #
+ # The third argument is an optional hash of conditions to further filter the objects.
+ # For example, here the user can only update published articles.
+ #
+ # can :update, Article, :published => true
+ #
+ # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
+ end
+end
diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb
new file mode 100644
index 00000000..8aff497a
--- /dev/null
+++ b/app/models/admin_ability.rb
@@ -0,0 +1,17 @@
+class AdminAbility
+ include CanCan::Ability
+
+ def initialize(user)
+ @user = user || User.new # for guest
+ @user.get_roles.each { |role| send(role.name.downcase) }
+ end
+
+ def organizer
+ can :manage, Event
+ end
+
+ def admin
+ organizer
+ can :manage, :all
+ end
+end
diff --git a/app/models/call_for_papers.rb b/app/models/call_for_papers.rb
new file mode 100644
index 00000000..f31a8127
--- /dev/null
+++ b/app/models/call_for_papers.rb
@@ -0,0 +1,7 @@
+class CallForPapers < ActiveRecord::Base
+ attr_accessible :start_date, :end_date, :hard_deadline, :description
+ belongs_to :conference
+
+ validates_presence_of :start_date, :end_date, :hard_deadline
+
+end
\ No newline at end of file
diff --git a/app/models/comment.rb b/app/models/comment.rb
new file mode 100644
index 00000000..8cccba85
--- /dev/null
+++ b/app/models/comment.rb
@@ -0,0 +1,48 @@
+class Comment < ActiveRecord::Base
+ acts_as_nested_set :scope => [:commentable_id, :commentable_type]
+ attr_accessible :commentable, :body, :user_id
+ validates_presence_of :body
+ validates_presence_of :user
+
+ # NOTE: install the acts_as_votable plugin if you
+ # want user to vote on the quality of comments.
+ #acts_as_votable
+
+ belongs_to :commentable, :polymorphic => true
+
+ # NOTE: Comments belong to a user
+ belongs_to :user
+
+ # Helper class method that allows you to build a comment
+ # by passing a commentable object, a user_id, and comment text
+ # example in readme
+ def self.build_from(obj, user_id, comment)
+ new \
+ :commentable => obj,
+ :body => comment,
+ :user_id => user_id
+ end
+
+ #helper method to check if a comment has children
+ def has_children?
+ self.children.any?
+ end
+
+ # Helper class method to lookup all comments assigned
+ # to all commentable types for a given user.
+ scope :find_comments_by_user, lambda { |user|
+ where(:user_id => user.id).order('created_at DESC')
+ }
+
+ # Helper class method to look up all comments for
+ # commentable class name and commentable id.
+ scope :find_comments_for_commentable, lambda { |commentable_str, commentable_id|
+ where(:commentable_type => commentable_str.to_s, :commentable_id => commentable_id).order('created_at DESC')
+ }
+
+ # Helper class method to look up a commentable object
+ # given the commentable class name and id
+ def self.find_commentable(commentable_str, commentable_id)
+ commentable_str.constantize.find(commentable_id)
+ end
+end
\ No newline at end of file
diff --git a/app/models/conference.rb b/app/models/conference.rb
new file mode 100644
index 00000000..6216cfda
--- /dev/null
+++ b/app/models/conference.rb
@@ -0,0 +1,87 @@
+class Conference < ActiveRecord::Base
+ attr_accessible :title, :short_title, :social_tag, :contact_email, :timezone, :html_export_path,
+ :start_date, :end_date, :rooms_attributes, :tracks_attributes, :cfp_open, :registration_open,
+ :event_types_attributes
+
+ has_one :call_for_papers, :dependent => :destroy
+ has_many :events, :dependent => :destroy
+ has_many :event_types, :dependent => :destroy
+ has_many :tracks, :dependent => :destroy
+ has_many :rooms, :dependent => :destroy
+ has_many :registrations, :dependent => :destroy
+
+ belongs_to :venue
+
+ accepts_nested_attributes_for :rooms, :reject_if => proc {|r| r["name"].blank?}, :allow_destroy => true
+ accepts_nested_attributes_for :tracks, :reject_if => proc {|r| r["name"].blank?}, :allow_destroy => true
+ accepts_nested_attributes_for :venue
+ accepts_nested_attributes_for :event_types, :allow_destroy => true
+
+ validates_presence_of :title,
+ :short_title,
+ :social_tag
+ validates_uniqueness_of :short_title
+ validates_format_of :short_title, :with => /^[a-zA-Z0-9_-]*$/
+ before_create :generate_guid
+ before_create :create_venue
+
+
+ def self.current
+ self.order("created_at DESC").first
+ end
+
+ def date_range_string
+ startstr = "Unknown - "
+ endstr = "Unknown"
+ if start_date.month == end_date.month && start_date.year == end_date.year
+ startstr = start_date.strftime("%B %d - ")
+ endstr = end_date.strftime("%d, %Y")
+ elsif start_date.month != end_date.month && start_date.year == end_date.year
+ startstr = start_date.strftime("%B %d - ")
+ endstr = end_date.strftime("%B %d, %Y")
+ else
+ startstr = start_date.strftime("%B %d, %Y - ")
+ endstr = end_date.strftime("%B %d, %Y")
+ end
+
+ result = startstr + endstr
+ result
+ end
+
+ def user_registered? user
+ return nil if user.nil?
+ return nil if user.person.nil?
+
+ if self.registrations.where(:person_id => user.person.id).count == 0
+ Rails.logger.debug("Returning false")
+ false
+ else
+ Rails.logger.debug("Returning true")
+ true
+ end
+ end
+
+ def cfp_open?
+ today = Date.current
+ cfp = self.call_for_papers
+ if !cfp.nil? && (cfp.start_date.. cfp.end_date).cover?(today)
+ return true
+ end
+
+ return false
+ end
+ private
+
+ def create_venue
+ self.venue_id = Venue.create.id
+ true
+ end
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while Person.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/event.rb b/app/models/event.rb
new file mode 100644
index 00000000..fc7fc5dc
--- /dev/null
+++ b/app/models/event.rb
@@ -0,0 +1,101 @@
+class Event < ActiveRecord::Base
+ include ActiveRecord::Transitions
+
+ attr_accessible :title, :subtitle, :abstract, :description, :event_type_id, :people_attributes, :person,
+ :proposal_additional_speakers
+ acts_as_commentable
+
+ has_many :event_people, :dependent => :destroy
+ has_many :event_attachments, :dependent => :destroy
+ has_many :people, :through => :event_people
+ belongs_to :event_type
+
+ belongs_to :track
+ belongs_to :room
+ belongs_to :conference
+
+ accepts_nested_attributes_for :event_people
+ accepts_nested_attributes_for :event_attachments, :allow_destroy => true, :reject_if => :all_blank
+ accepts_nested_attributes_for :people
+ before_create :generate_guid
+ validate :abstract_limit
+
+ state_machine :initial => :new do
+ state :new
+ state :review
+ state :withdrawn
+ state :accepted
+ state :unconfirmed
+ state :confirmed
+ state :canceled
+ state :rejected
+
+ event :start_review do
+ transitions :to => :review, :from => [:new, :rejected]
+ end
+ event :withdraw do
+ transitions :to => :withdrawn, :from => [:new, :review, :unconfirmed]
+ end
+ event :accept do
+ transitions :to => :unconfirmed, :from => [:new, :review], :on_transition => :process_acceptance
+ end
+ event :unconfirm do
+ transitions :to => :review, :from=>[:confirmed]
+ end
+ event :confirm do
+ transitions :to => :confirmed, :from => :unconfirmed
+ end
+ event :cancel do
+ transitions :to => :canceled, :from => [:unconfirmed, :confirmed]
+ end
+ event :reject do
+ transitions :to => :rejected, :from => [:new, :review], :on_transition => :process_rejection
+ end
+ end
+
+ def submitter
+ result = self.event_people.where(:event_role => "submitter").first
+ if !result.nil?
+ result.person
+ else
+ nil
+ end
+ end
+
+ def transition_possible?(transition)
+ self.class.state_machine.events_for(self.current_state).include?(transition)
+ end
+
+ def process_acceptance(options)
+# if options[:send_mail]
+ # self.event_people.presenter.each do |event_person|
+ # event_person.generate_token!
+ # SelectionNotification.acceptance_notification(event_person).deliver
+ # end
+ # end
+
+ end
+
+ def abstract_word_count
+ if self.abstract.nil?
+ 0
+ else
+ self.abstract.split.size
+ end
+ end
+ private
+
+ def abstract_limit
+ if self.abstract.split.size > 250
+ errors.add(:abstract, "cannot have more than 250 words")
+ end
+ end
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while self.class.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/event_attachment.rb b/app/models/event_attachment.rb
new file mode 100644
index 00000000..9d33fa41
--- /dev/null
+++ b/app/models/event_attachment.rb
@@ -0,0 +1,28 @@
+class EventAttachment < ActiveRecord::Base
+ belongs_to :event
+ scope :public, where(:public => true)
+ attr_accessible :public, :attachment, :event_id, :title
+
+ has_attached_file :attachment, :path => ":rails_root/storage/:rails_env/attachments/:id/:style/:basename.:extension"
+ include Rails.application.routes.url_helpers
+
+ def to_jq_upload
+ {
+ "name" => read_attribute(:attachment_file_name),
+ "size" => read_attribute(:attachment_file_size),
+ "title" => read_attribute(:title),
+ "public" => read_attribute(:public),
+ #"url" => attachment.url(:original),
+ "url" => conference_proposal_event_attachment_path(self.event.conference.short_title, self.event_id, self.id),
+ "delete_url" => conference_proposal_event_attachment_path(self.event.conference.short_title, self.event_id, self.id),
+ "delete_type" => "DELETE"
+ }
+
+ end
+ #:path => ":rails_root/public/system/:attachment/:id/:style/:filename",
+ # :url => "/system/:attachment/:id/:style/:filename"
+
+ #has_paper_trail :meta => {:associated_id => :event_id, :associated_type => "Event"}
+
+
+end
diff --git a/app/models/event_person.rb b/app/models/event_person.rb
new file mode 100644
index 00000000..ba0721a2
--- /dev/null
+++ b/app/models/event_person.rb
@@ -0,0 +1,8 @@
+class EventPerson < ActiveRecord::Base
+ attr_accessible :event, :person, :person_id, :event_role
+ # TODO Do we need these roles?
+ ROLES = [["Speaker","speaker"], ["Submitter","submitter"], ["Moderator","moderator"]]
+
+ belongs_to :event
+ belongs_to :person
+end
\ No newline at end of file
diff --git a/app/models/event_type.rb b/app/models/event_type.rb
new file mode 100644
index 00000000..49afdbbb
--- /dev/null
+++ b/app/models/event_type.rb
@@ -0,0 +1,5 @@
+class EventType < ActiveRecord::Base
+ attr_accessible :title, :length
+
+ belongs_to :conference
+end
diff --git a/app/models/person.rb b/app/models/person.rb
new file mode 100644
index 00000000..173f0088
--- /dev/null
+++ b/app/models/person.rb
@@ -0,0 +1,71 @@
+class Person < ActiveRecord::Base
+ attr_accessible :email, :first_name, :last_name, :public_name, :biography, :company
+
+ has_many :event_people, :dependent => :destroy
+ has_many :events, :through => :event_people, :uniq => true
+ has_many :registrations, :dependent => :destroy
+ validates :first_name, :presence => true
+ validates :last_name, :presence => true
+ validate :biography_limit
+
+ before_create :generate_guid
+ before_save :set_public_name
+
+ def to_s
+ if self.public_name.empty?
+ self.first_name + " " + self.last_name
+ else
+ self.public_name
+ end
+ end
+
+ def withdraw_proposal id
+ proposal = self.events.find_by_id(id)
+ if !proposal.nil?
+ proposal.withdraw
+ proposal.save
+ end
+ end
+
+ def attending_conference? conference
+ Registration.where(:conference_id => conference.id,
+ :person_id => self.id).count
+ end
+
+ def proposals conference
+ self.events.where("conference_id = ? AND state != ? AND state != ?", conference.id, "withdrawn", "rejected")
+ end
+
+ def proposal_count conference
+ proposals(conference).count
+ end
+
+ def biography_word_count
+ if self.biography.nil?
+ 0
+ else
+ self.biography.split.size
+ end
+ end
+
+ private
+ def biography_limit
+ if self.biography.split.size > 150
+ errors.add(:abstract, "cannot have more than 150 words")
+ end
+ end
+
+ def set_public_name
+ if public_name.empty?
+ self.public_name = "#{first_name} #{last_name}"
+ end
+ end
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while Person.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/registration.rb b/app/models/registration.rb
new file mode 100644
index 00000000..b8886570
--- /dev/null
+++ b/app/models/registration.rb
@@ -0,0 +1,9 @@
+class Registration < ActiveRecord::Base
+ belongs_to :person
+ belongs_to :conference
+
+ attr_accessible :person_id, :conference_id, :attending_social_events, :attending_social_events_with_partner,
+ :using_affiliated_lodging, :arrival, :departure, :person_attributes
+ accepts_nested_attributes_for :person
+
+end
\ No newline at end of file
diff --git a/app/models/role.rb b/app/models/role.rb
new file mode 100644
index 00000000..0fb24257
--- /dev/null
+++ b/app/models/role.rb
@@ -0,0 +1,4 @@
+class Role < ActiveRecord::Base
+ attr_accessible :name
+ has_and_belongs_to_many :users
+end
\ No newline at end of file
diff --git a/app/models/room.rb b/app/models/room.rb
new file mode 100644
index 00000000..ba68566e
--- /dev/null
+++ b/app/models/room.rb
@@ -0,0 +1,18 @@
+class Room < ActiveRecord::Base
+ attr_accessible :name, :size, :public
+
+ belongs_to :conference
+ has_many :events
+
+ before_create :generate_guid
+
+ private
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while Person.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/track.rb b/app/models/track.rb
new file mode 100644
index 00000000..7c6f685f
--- /dev/null
+++ b/app/models/track.rb
@@ -0,0 +1,17 @@
+class Track < ActiveRecord::Base
+ attr_accessible :name, :description, :color
+
+ belongs_to :conference
+
+ before_create :generate_guid
+
+ private
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while Person.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 00000000..9e262977
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,55 @@
+class User < ActiveRecord::Base
+
+ # Include default devise modules. Others available are:
+ # :token_authenticatable, :confirmable,
+ # :lockable, :timeoutable and :omniauthable
+ devise :database_authenticatable, :registerable,
+ :recoverable, :rememberable, :trackable, :validatable
+
+ has_and_belongs_to_many :roles
+ has_one :person
+
+ attr_accessible :email, :password, :password_confirmation, :remember_me, :role_id, :role_ids, :person_attributes
+ accepts_nested_attributes_for :person
+ accepts_nested_attributes_for :roles
+
+ before_save :setup_role
+ before_create :create_person
+
+ def role?(role)
+ Rails.logger.debug("Checking role in user")
+ return !!self.roles.find_by_name(role.to_s.camelize)
+ end
+
+ def get_roles
+ return self.roles
+ end
+
+ def setup_role
+ if self.role_ids.empty?
+ self.role_ids = [1]
+ end
+ end
+
+ def popup_details
+ details = "Sign-in Count "
+ details += "#{self.sign_in_count} "
+ details += "Current Sign-in "
+ details += "#{self.current_sign_in_at} "
+ details += "Last Sign-in "
+ details += "#{self.last_sign_in_at} "
+ details += "Current Sign-in IP "
+ details += "#{self.current_sign_in_ip} "
+ details += "Last Sign-in IP "
+ details += "#{self.last_sign_in_ip} "
+ details += "Created at "
+ details += "#{self.created_at} "
+ end
+
+ private
+ def create_person
+ # TODO Search people for existing email address, add to their account
+ build_person(:email => self.email)
+ true
+ end
+end
diff --git a/app/models/venue.rb b/app/models/venue.rb
new file mode 100644
index 00000000..e0251f7c
--- /dev/null
+++ b/app/models/venue.rb
@@ -0,0 +1,14 @@
+class Venue < ActiveRecord::Base
+ has_many :conferences
+ before_create :generate_guid
+
+ private
+
+ def generate_guid
+ begin
+ guid = SecureRandom.urlsafe_base64
+ end while Venue.where(:guid => guid).exists?
+ self.guid = guid
+ end
+
+end
diff --git a/app/views/admin/callforpapers/show.html.haml b/app/views/admin/callforpapers/show.html.haml
new file mode 100644
index 00000000..5b388a82
--- /dev/null
+++ b/app/views/admin/callforpapers/show.html.haml
@@ -0,0 +1,11 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Call for Papers"
+ .span9
+ = semantic_form_for(@cfp, :url => admin_conference_cfp_update_path(@conference.short_title),:html => {:multipart => true}) do |f|
+ = f.input :start_date, :as => :string, :input_html => { :id => "conference-start-datepicker", :readonly => "readonly" }
+ = f.input :end_date, :as => :string, :input_html => { :id => "conference-end-datepicker", :readonly => "readonly" }
+ = f.input :hard_deadline, :as => :string, :input_html => { :id => "cfp-hard-datepicker", :readonly => "readonly" }
+ = f.input :description, :hint => "Welcome text for the Call for Papers"
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/admin/conference/_sidebar.html.haml b/app/views/admin/conference/_sidebar.html.haml
new file mode 100644
index 00000000..cb8b4397
--- /dev/null
+++ b/app/views/admin/conference/_sidebar.html.haml
@@ -0,0 +1,26 @@
+.well.sidebar-nav
+ %ul.nav.nav-list
+ - if activated == "Conference"
+ %li.active= link_to "Conference", "#"
+ - else
+ %li= link_to "Conference", admin_conference_path(@conference.short_title)
+ - if activated == "Call for Papers"
+ %li.active= link_to "Call for Papers", "#"
+ - else
+ %li= link_to "Call for Papers", admin_conference_cfp_info_path(@conference.short_title)
+ - if activated == "Venue"
+ %li.active= link_to "Venue", "#"
+ - else
+ %li= link_to "Venue", admin_conference_venue_info_path(@conference.short_title)
+ - if activated == "Rooms"
+ %li.active= link_to "Rooms", "#"
+ - else
+ %li= link_to "Rooms", admin_conference_rooms_list_path(@conference.short_title)
+ - if activated == "Tracks"
+ %li.active= link_to "Tracks", "#"
+ - else
+ %li= link_to "Tracks", admin_conference_tracks_list_path(@conference.short_title)
+ - if activated == "Event Types"
+ %li.active= link_to "Event Types", "#"
+ - else
+ %li= link_to "Event Types", admin_conference_eventtype_list_path(@conference.short_title)
\ No newline at end of file
diff --git a/app/views/admin/conference/index.html.haml b/app/views/admin/conference/index.html.haml
new file mode 100644
index 00000000..737680e7
--- /dev/null
+++ b/app/views/admin/conference/index.html.haml
@@ -0,0 +1,3 @@
+%ul
+ - @conferences.each do |conference|
+ %li= link_to conference.title, admin_conference_path(conference.short_title)
diff --git a/app/views/admin/conference/new.html.haml b/app/views/admin/conference/new.html.haml
new file mode 100644
index 00000000..76301c7f
--- /dev/null
+++ b/app/views/admin/conference/new.html.haml
@@ -0,0 +1,13 @@
+= semantic_form_for(@conference, :url => admin_conference_index_path) do |f|
+ = f.inputs "Basic Information" do
+ = f.input :title, :hint => "The name of your conference as it shall appear throughout the site. Example: 'OpenSUSE Conference 2013'"
+ = f.input :short_title, :hint => "A short and unique handle for your conference, using only lower-case letters, numbers and underscores. This will be used to identify your conference in URLs etc. Example: 'froscon2011'"
+ = f.input :social_tag, :hint => "The hashtag you'll use on Twitter and Google+. Don't include the '#' sign!'"
+ = f.input :contact_email, :hint => "Contact email address for your conference. Will be used as reply-to address in emails sent out by the system."
+ = f.inputs "Scheduling" do
+ = f.input :timezone, :as => :time_zone, :hint => "Please select in what time zone your conference will take place. This is needed to correctly display times for your events."
+ = f.input :start_date, :as => :string, :input_html => { :id => "conference-start-datepicker", :readonly => "readonly" }
+ = f.input :end_date, :as => :string, :input_html => { :id => "conference-end-datepicker", :readonly => "readonly" }
+ = f.input :html_export_path, :hint => "The path for static HTML exports"
+ = f.actions do
+ = f.action :submit, :button_html => {:class => "btn primary"}
diff --git a/app/views/admin/conference/show.html.haml b/app/views/admin/conference/show.html.haml
new file mode 100644
index 00000000..4d4df114
--- /dev/null
+++ b/app/views/admin/conference/show.html.haml
@@ -0,0 +1,17 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Conference"
+ .span9
+ = semantic_form_for(@conference, :url => admin_conference_path(@conference.short_title),:html => {:multipart => true}) do |f|
+ = f.input :title, :hint => "The full name of the conference, such as 'OpenSUSE Conference 2013'"
+ = f.input :short_title, :hint => "A short title, such as 'osc2013', to be used in URLs"
+ = f.input :social_tag, :hint => "The hashtag you'll use on Twitter and Google+. Don't include the '#' sign!'"
+ = f.input :contact_email, :hint => "Contact email address for your conference. Will be used as reply-to address in emails sent out by the system."
+ = f.input :timezone, :as => :time_zone, :hint => "The conference time zone"
+ = f.input :start_date, :as => :string, :input_html => { :id => "conference-start-datepicker", :readonly => "readonly" }
+ = f.input :end_date, :as => :string, :input_html => { :id => "conference-end-datepicker", :readonly => "readonly" }
+ = f.input :html_export_path, :hint => "The path for static HTML exports"
+ = f.input :cfp_open, :label => false, :hint => "Allow people to submit papers?"
+ = f.input :registration_open, :label => false, :hint => "Allow people to register as attendees?"
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/admin/events/_person_fields.html.haml b/app/views/admin/events/_person_fields.html.haml
new file mode 100644
index 00000000..4999c2ab
--- /dev/null
+++ b/app/views/admin/events/_person_fields.html.haml
@@ -0,0 +1,6 @@
+.nested-fields
+ = f.inputs do
+ = f.input :email
+ = f.input :public_name
+ = remove_association_link :person, f
+
diff --git a/app/views/admin/events/edit.html.haml b/app/views/admin/events/edit.html.haml
new file mode 100644
index 00000000..9372b87b
--- /dev/null
+++ b/app/views/admin/events/edit.html.haml
@@ -0,0 +1,2 @@
+.container
+ = render 'proposal/form'
\ No newline at end of file
diff --git a/app/views/admin/events/index.html.haml b/app/views/admin/events/index.html.haml
new file mode 100644
index 00000000..902f8d43
--- /dev/null
+++ b/app/views/admin/events/index.html.haml
@@ -0,0 +1,48 @@
+.row-fluid
+ .span12
+ %table.table.table-striped.table-bordered.table-hover
+ %thead
+ %th
+ %b Title
+ %th
+ %b Submitter
+ %th
+ %b Type
+ %th
+ %b State
+ - @events.each do |event|
+ %tr
+ %td
+ =link_to event.title, admin_conference_event_path(@conference.short_title, event)
+ %td
+ - if !event.submitter.nil?
+ =link_to event.submitter.public_name, admin_person_path(event.submitter)
+ - else
+ Unknown submitter
+ %td
+ = event.event_type.title
+ %td
+ - if event.state == "withdrawn"
+ Withdrawn
+ - else
+ .dropdown
+ = link_to "#", :class => "dropdown-toggle" do
+ = event.state.humanize
+
+ %ul.dropdown-menu
+ - if event.transition_possible? :accept
+ %li= link_to "Accept event (no email)", update_state_admin_conference_event_path(@conference.short_title, event, :transition => :accept, :send_mail => false),
+ :method => :put, :hint => "Accept this event without sending an automated email."
+ - if event.transition_possible? :reject
+ %li= link_to "Reject event (no email)", update_state_admin_conference_event_path(@conference.short_title, event, :transition => :reject, :send_mail => false),
+ :method => :put, :confirm => "Are you sure?",
+ :hint => "Reject this event without sending an automated email."
+ - if event.transition_possible? :start_review
+ %li= link_to "Start review", update_state_admin_conference_event_path(@conference.short_title, event, :transition => :start_review),
+ :method => :put
+ - if event.transition_possible? :confirm
+ %li= link_to "Confirm event", update_state_admin_conference_event_path(@conference.short_title, event, :transition => :confirm),
+ :method => :put, :hint => "Confirm that the speaker(s) will be present and that the event will actually take place."
+ - if event.transition_possible? :cancel
+ %li= link_to "Cancel event", update_state_admin_conference_event_path(@conference.short_title, event, :transition => :cancel),
+ :method => :put, :hint => "Mark this event as cancelled. Usually this means that the speakers had to cancel their appearance."
diff --git a/app/views/admin/events/show.html.haml b/app/views/admin/events/show.html.haml
new file mode 100644
index 00000000..bdd42183
--- /dev/null
+++ b/app/views/admin/events/show.html.haml
@@ -0,0 +1,112 @@
+.row-fluid
+ .span10
+ %h3
+ = @event.title
+ %i= @event.subtitle
+
+ .span2
+ = link_to "Edit Submission", edit_admin_conference_event_path(@conference.short_title, @event),
+ :class => "btn btn-mini btn-primary pull-right", :style => "margin-top:20px;"
+
+.row-fluid
+ .span12
+ %table.table
+ %tr
+ %td
+ %b State
+ %td
+ .dropdown
+ = link_to "#", :class => "dropdown-toggle" do
+ = @event.state.humanize
+
+ %ul.dropdown-menu
+ - if @event.transition_possible? :accept
+ %li= link_to "Accept event (no email)", update_state_admin_conference_event_path(@conference.short_title, @event, :transition => :accept, :send_mail => false),
+ :method => :put, :hint => "Accept this event without sending an automated email."
+ - if @event.transition_possible? :reject
+ %li= link_to "Reject event (no email)", update_state_admin_conference_event_path(@conference.short_title, @event, :transition => :reject, :send_mail => false),
+ :method => :put, :confirm => "Are you sure?",
+ :hint => "Reject this event without sending an automated email."
+ - if @event.transition_possible? :start_review
+ %li= link_to "Start review", update_state_admin_conference_event_path(@conference.short_title, @event, :transition => :start_review),
+ :method => :put
+ - if @event.transition_possible? :confirm
+ %li= link_to "Confirm event", update_state_admin_conference_event_path(@conference.short_title, @event, :transition => :confirm),
+ :method => :put, :hint => "Confirm that the speaker(s) will be present and that the event will actually take place."
+ - if @event.transition_possible? :cancel
+ %li= link_to "Cancel event", update_state_admin_conference_event_path(@conference.short_title, @event, :transition => :cancel),
+ :method => :put, :hint => "Mark this event as cancelled. Usually this means that the speakers had to cancel their appearance."
+
+ %tr
+ %td
+ %b Submitter
+ %td
+ = link_to @event.submitter.public_name, admin_person_path(@conference.short_title, @event.submitter)
+ (
+ = link_to @event.submitter.email, "mailto: #{@event.submitter.email}"
+ )
+ %tr
+ %td
+ %b Biography
+ %td
+ = simple_format(@event.submitter.biography)
+ %tr
+ %td
+ %b Submitted on
+ %td= @event.created_at
+ %tr
+ %td
+ %b Last updated on
+ %td= @event.updated_at
+ %tr
+ %td
+ %b Abstract
+ %td= simple_format(@event.abstract)
+ %tr
+ %td
+ %b Description
+ %td= simple_format(@event.description)
+ - if @event.event_attachments.size > 0
+ %table.table
+ %thead
+ %th
+ %b Filename
+ %th
+ %b Title
+ %th
+ %b Size
+ %th
+ %b Public?
+ %th
+ %tbody
+ - @event.event_attachments.each do |a|
+ %tr
+ %td
+ = link_to a.attachment_file_name, conference_proposal_event_attachment_path(@conference.short_title, @event.id, a.id)
+ %td
+ = a.title
+ %td
+ = number_to_human_size a.attachment_file_size
+ %td
+ - if a.public?
+ Public
+ - else
+ Not Public
+ %td
+ = link_to "Delete", conference_proposal_event_attachment_path(@conference.short_title, @event.id, a.id),
+ :method => :delete, :confirm => "Are you sure you want to delete this attachment? It's permanant!"
+
+.row-fluid
+ = link_to "Comments (#{@comment_count})", "#", :id => "event-comment-link"
+ #comments-div
+ %hr
+ %ul.media
+ %div
+ .row-fluid
+ = form_tag(comment_admin_conference_event_path(@conference.short_title, @event.id), :method => :post) do
+ = text_area_tag(:comment, "")
+ = submit_tag "Add Comment", :class => "btn btn-primary pull-right"
+ %br
+ - @comments.each do |comment|
+ %div
+ = raw format_comments(comment)
\ No newline at end of file
diff --git a/app/views/admin/eventtype/_event_type_fields.html.erb b/app/views/admin/eventtype/_event_type_fields.html.erb
new file mode 100644
index 00000000..bca9f61b
--- /dev/null
+++ b/app/views/admin/eventtype/_event_type_fields.html.erb
@@ -0,0 +1,7 @@
+
+ <%= f.inputs do %>
+ <%= f.input :title %>
+ <%= f.input :length, :input_html => {:size => 3} %>
+ <%= remove_association_link :event_type, f %>
+ <% end %>
+
diff --git a/app/views/admin/eventtype/event_type_list.html.haml b/app/views/admin/eventtype/event_type_list.html.haml
new file mode 100644
index 00000000..d1715ce5
--- /dev/null
+++ b/app/views/admin/eventtype/event_type_list.html.haml
@@ -0,0 +1,8 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Event Types"
+ .span9
+ = semantic_form_for(@conference, :url => admin_conference_eventtype_update_path(@conference.short_title)) do |f|
+ = dynamic_association :event_types, "Event Types", f
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/admin/people/index.html.haml b/app/views/admin/people/index.html.haml
new file mode 100644
index 00000000..af8a64bf
--- /dev/null
+++ b/app/views/admin/people/index.html.haml
@@ -0,0 +1,18 @@
+%table.table.table-striped.table-bordered.table-hover
+ %thead
+ %th
+ %b Last Name
+ %th
+ %b First Name
+ %th
+ %b Public Name
+ %th
+ %b # of Conference Registrations
+ %th
+ - @people.each do |person|
+ %tr
+ %td= person.last_name
+ %td= person.first_name
+ %td= person.public_name
+ %td= person.registrations.count
+ %td= link_to "View", admin_person_path(person)
\ No newline at end of file
diff --git a/app/views/admin/people/show.html.haml b/app/views/admin/people/show.html.haml
new file mode 100644
index 00000000..c996aec3
--- /dev/null
+++ b/app/views/admin/people/show.html.haml
@@ -0,0 +1,49 @@
+%table.table
+ %tr
+ %td
+ %b First Name
+ %td
+ = @person.first_name
+ %tr
+ %td
+ %b Last Name
+ %td
+ = @person.last_name
+ %tr
+ %td
+ %b Public Name
+ %td
+ = @person.public_name
+ %tr
+ %td
+ %b Company
+ %td
+ = @person.company
+ %tr
+ %td
+ %b Email
+ %td
+ = @person.email
+ %tr
+ %td
+ %b Created At
+ %td
+ = @person.created_at
+ %tr
+ %td
+ %b Updated At
+ %td
+ = @person.updated_at
+ %tr
+ %td
+ %b Biography
+ %td
+ = @person.biography
+ %tr
+ %td
+ %b Registered to attend
+ %td
+ - if @person.registrations.count == 0
+ None
+ - else
+ = @person.registrations.map { |r| r.conference.title }.join ','
\ No newline at end of file
diff --git a/app/views/admin/registrations/show.html.haml b/app/views/admin/registrations/show.html.haml
new file mode 100644
index 00000000..a3e5d168
--- /dev/null
+++ b/app/views/admin/registrations/show.html.haml
@@ -0,0 +1,48 @@
+.row-fluid
+ .span12
+ .pull-right{:style=>"margin-top:20px; margin-bottom:20px;"}
+ = link_to "Export PDF", admin_conference_registrations_path(@conference.short_title, :format => :pdf), :class => "btn btn-success"
+.row-fluid
+ .span12
+ %table.table.table-striped.table-bordered.table-hover
+ %thead
+ %th
+ %b Last Name
+ %th
+ %b First Name
+ %th
+ %b Public Name
+ %th
+ %b Email
+ %th
+ %b Attending Social Events
+ %th
+ %b Attending With Partner
+ %th
+ %b Arrival Date
+ %th
+ %b Departure Date
+ %th
+ %th
+ - @registrations.each do |registration|
+ %tr
+ %td
+ = registration.last_name
+ %td
+ = registration.first_name
+ %td
+ = registration.public_name
+ %td
+ = registration.email
+ %td
+ = registration.attending_social_events
+ %td
+ = registration.attending_social_events_with_partner
+ %td
+ = registration.arrival
+ %td
+ = registration.departure
+ %td
+ = link_to "Edit", "#", :class => "btn btn-primary"
+ %td
+ = link_to "Delete", "#", :class => "btn btn-danger", :confirm => "Really delete registration for #{registration.public_name}?"
\ No newline at end of file
diff --git a/app/views/admin/registrations/show.pdf.prawn b/app/views/admin/registrations/show.pdf.prawn
new file mode 100644
index 00000000..38a8031a
--- /dev/null
+++ b/app/views/admin/registrations/show.pdf.prawn
@@ -0,0 +1,38 @@
+prawn_document(:force_download=>true, :filename => @pdf_filename) do |pdf|
+ table_array = []
+ header_array = [" ",
+ "Last Name",
+ "First Name",
+ "Public Name",
+ "Email",
+ "Attending Social Events",
+ "Attending With Partner",
+ "Arrival Date",
+ "Departure Date"]
+ table_array << header_array
+ @registrations.each do |registration|
+ row = []
+ row << ""
+ row << registration.last_name
+ row << registration.first_name
+ row << registration.public_name
+ row << registration.email
+ if registration.attending_social_events
+ row << "X"
+ else
+ row << " "
+ end
+
+ if registration.attending_social_events_with_partner.to_s
+ row << "X"
+ else
+ row << " "
+ end
+ row << registration.arrival
+ row << registration.departure
+ table_array << row
+ end
+
+ pdf.text "#{@conference.title} Registrations", :font_size => 25
+ pdf.table table_array, :header => true, :cell_style => {:size => 5, :border_width => 1}
+end
\ No newline at end of file
diff --git a/app/views/admin/rooms/_room_fields.html.erb b/app/views/admin/rooms/_room_fields.html.erb
new file mode 100644
index 00000000..203176e8
--- /dev/null
+++ b/app/views/admin/rooms/_room_fields.html.erb
@@ -0,0 +1,8 @@
+
+ <%= f.inputs do %>
+ <%= f.input :name %>
+ <%= f.input :size, :input_html => {:size => 5} %>
+ <%= f.input :public, :as => :boolean %>
+ <%= remove_association_link :room, f %>
+ <% end %>
+
diff --git a/app/views/admin/rooms/rooms_list.html.haml b/app/views/admin/rooms/rooms_list.html.haml
new file mode 100644
index 00000000..76c9b7eb
--- /dev/null
+++ b/app/views/admin/rooms/rooms_list.html.haml
@@ -0,0 +1,8 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Rooms"
+ .span9
+ = semantic_form_for(@conference, :url => admin_conference_rooms_update_path(@conference.short_title)) do |f|
+ = dynamic_association :rooms, "Rooms", f
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/admin/tracks/_track_fields.html.erb b/app/views/admin/tracks/_track_fields.html.erb
new file mode 100644
index 00000000..b40c76d8
--- /dev/null
+++ b/app/views/admin/tracks/_track_fields.html.erb
@@ -0,0 +1,8 @@
+
+ <%= f.inputs do %>
+ <%= f.input :name %>
+ <%= f.input :color, :input_html => {:size => 6} %>
+ <%= f.input :description, :input_html => {:rows => 2 } %>
+ <%= remove_association_link :track, f %>
+ <% end %>
+
diff --git a/app/views/admin/tracks/tracks_list.html.haml b/app/views/admin/tracks/tracks_list.html.haml
new file mode 100644
index 00000000..7ada0212
--- /dev/null
+++ b/app/views/admin/tracks/tracks_list.html.haml
@@ -0,0 +1,8 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Tracks"
+ .span9
+ = semantic_form_for(@conference, :url => admin_conference_tracks_update_path(@conference.short_title)) do |f|
+ = dynamic_association :tracks, "Tracks", f
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
new file mode 100644
index 00000000..bd0d5f64
--- /dev/null
+++ b/app/views/admin/users/index.html.haml
@@ -0,0 +1,57 @@
+.container-fluid
+ .row-fluid
+ .span12
+ %table.table.table-striped.table-bordered.table-hover
+ %thead
+ %th
+ %b Email
+ %th
+ %b Last Name
+ %th
+ %b First Name
+ %th
+ %b Public Name
+ %th
+ %b Roles
+ %th
+ %th
+ - @users.each do |user|
+ %tr
+ %td
+ = user.email
+ %td
+ = user.last_name
+ %td
+ = user.first_name
+ %td
+ = user.public_name
+ %td
+ = user.roles.map { |role| role.name }.join ','
+ %td
+ .modal.hide.fade{:id => "user-role-selection-#{user.id}", "role" => "dialog", "aria-hidden" => "true"}
+ .modal-header
+ %button{"type"=>"button", :class=>"close", "data-dismiss"=>"modal", "aria-hidden"=>"true"}
+ ×
+ %h3{:id => "role-selector-header-#{user.id}"}
+ Modifying Roles
+ .modal-body
+ - if current_user == user
+ You cannot modify your own role!
+ %br
+ %button{:class=> "btn btn-danger", "data-dismiss"=> "modal", "aria-hidden"=>"true"}
+ Cancel
+
+ - else
+ = "Give #{user.public_name} (#{user.email}) the following roles:"
+ = semantic_form_for(user, :url => admin_user_path(user), :method => :put) do |f|
+ = f.input :roles, :label => false
+ %button{:class=> "btn btn-danger", "data-dismiss"=> "modal", "aria-hidden"=>"true"}
+ Cancel
+ = f.action :submit, :as => :button, :button_html => {:value => "Save", :class => "btn btn-primary"}
+ =link_to "Modify Roles", "#", "data-toggle" => "modal", "data-target" => "#user-role-selection-#{user.id}}"
+ %td
+ =link_to "Details", "#", :class => "user-details-popover", "data-trigger" => "click", "data-placement" => "bottom",
+ "data-html" => "true",
+ "data-content" => user.popup_details,
+ "data-original-title" => ""
+
diff --git a/app/views/admin/venue/venue_info.html.haml b/app/views/admin/venue/venue_info.html.haml
new file mode 100644
index 00000000..a4431b76
--- /dev/null
+++ b/app/views/admin/venue/venue_info.html.haml
@@ -0,0 +1,11 @@
+.container-fluid
+ .row-fluid
+ .span3
+ = render 'admin/conference/sidebar', :activated => "Venue"
+ .span9
+ = semantic_form_for(@venue, :url => admin_conference_venue_update_path(@conference.short_title),:html => {:multipart => true}) do |f|
+ = f.input :name
+ = f.input :address
+ = f.input :website
+ = f.input :description, :input_html => { :rows => 10, :cols => 20 }
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
\ No newline at end of file
diff --git a/app/views/conference_registration/register.html.haml b/app/views/conference_registration/register.html.haml
new file mode 100644
index 00000000..3774998b
--- /dev/null
+++ b/app/views/conference_registration/register.html.haml
@@ -0,0 +1,22 @@
+.container-fluid
+ .row-fluid
+ .span12
+ %h3.pull-left
+ Registration for
+ = @conference.title
+ .row-fluid
+ .span12
+ = semantic_form_for(@registration, :url => register_conference_path(@conference.short_title), :html => { :method => :put }) do |f|
+ = f.fields_for :person do
+ = f.inputs :public_name, :for => :person
+ = f.input :attending_social_events, :label => false
+ = f.input :attending_social_events_with_partner, :label => false
+ = f.input :using_affiliated_lodging, :label => false
+ = f.input :arrival, :as => :string, :input_html => { :id => "conference-start-datepicker", :readonly => "readonly" }
+ = f.input :departure, :as => :string, :input_html => { :id => "conference-end-datepicker", :readonly => "readonly" }
+ - if @registered
+ = f.action :submit, :button_html => { :value => "Update Registration", :class => "btn btn-primary" }
+ = link_to "Unregister", register_conference_path(@conference.short_title),:method => :delete, :class => "btn btn-danger",
+ :confirm => "Are you sure you want to unregister?"
+ - else
+ = f.action :submit, :button_html => { :value => "Register", :class => "btn btn-primary" }
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
new file mode 100644
index 00000000..81e4472d
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,12 @@
+Resend confirmation instructions
+
+<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend confirmation instructions" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
new file mode 100644
index 00000000..a5c4585e
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
+Welcome <%= @resource.email %>!
+
+You can confirm your account email through the link below:
+
+<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb
new file mode 100644
index 00000000..ae9e888a
--- /dev/null
+++ b/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+Hello <%= @resource.email %>!
+
+Someone has requested a link to change your password, and you can do this through the link below.
+
+<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>
+
+If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
new file mode 100644
index 00000000..2263c219
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+Hello <%= @resource.email %>!
+
+Your account has been locked due to an excessive amount of unsuccessful sign in attempts.
+
+Click the link below to unlock your account:
+
+<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
new file mode 100644
index 00000000..fe620ef4
--- /dev/null
+++ b/app/views/devise/passwords/edit.html.erb
@@ -0,0 +1,16 @@
+Change your password
+
+<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= devise_error_messages! %>
+ <%= f.hidden_field :reset_password_token %>
+
+ <%= f.label :password, "New password" %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit "Change my password" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
new file mode 100644
index 00000000..23501646
--- /dev/null
+++ b/app/views/devise/passwords/new.html.erb
@@ -0,0 +1,12 @@
+Forgot your password?
+
+<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Send me reset password instructions" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/registrations/_edit_account.html.erb b/app/views/devise/registrations/_edit_account.html.erb
new file mode 100644
index 00000000..1945f846
--- /dev/null
+++ b/app/views/devise/registrations/_edit_account.html.erb
@@ -0,0 +1,21 @@
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, :autocomplete => "off" %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password %>
+
+ <%= f.submit "Update" %>
+<% end %>
+
+Cancel my account
+
+Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %>.
+
+<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/_edit_profile.html.erb b/app/views/devise/registrations/_edit_profile.html.erb
new file mode 100644
index 00000000..9ea5b36a
--- /dev/null
+++ b/app/views/devise/registrations/_edit_profile.html.erb
@@ -0,0 +1,20 @@
+<% if false %>
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= f.fields_for :person do |p| %>
+ <%= p.label :first_name %>
+ <%= p.text_field :first_name %>
+
+ <%= p.label :last_name %>
+ <%= p.text_field :last_name %>
+ <% end %>
+ <%= f.submit "Update" %>
+<% end %>
+
+<% end %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= f.fields_for :person do |p| %>
+ <%= f.inputs :first_name, :last_name, :for => :person %>
+ <% end %>
+ <%= f.submit "Update" %>
+<% end %>
diff --git a/app/views/devise/registrations/edit.html.erb-backup b/app/views/devise/registrations/edit.html.erb-backup
new file mode 100644
index 00000000..0abfb3fc
--- /dev/null
+++ b/app/views/devise/registrations/edit.html.erb-backup
@@ -0,0 +1,18 @@
+<%= devise_error_messages! %>
+<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+
+ <%= f.inputs :name => "Profile" do %>
+ <%= f.fields_for :person do |p| %>
+ <%= f.inputs :first_name, :last_name, :for => :person %>
+ <% end %>
+ <% end %>
+
+ <%= f.inputs :name => "Account" do %>
+ <%= f.input :email %>
+ <%= f.input :password, :hint => "(Leave blank if you don't want to change it)", :input_html => {:autocomplete => "off"} %>
+ <%= f.input :password_confirmation, :input_html => {:autocomplete => "off"} %>
+ <%= f.input :current_password, :input_html => {:autocomplete => "off"}, :hint => "(we need your current password to confirm your changes)" %>
+ <% end %>
+
+<%= f.submit "Update" %>
+<% end %>
diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml
new file mode 100644
index 00000000..5a88d768
--- /dev/null
+++ b/app/views/devise/registrations/edit.html.haml
@@ -0,0 +1,19 @@
+.container
+ .row-fluid
+ = semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f|
+ = f.inputs :name => "Profile" do
+ = f.fields_for :person do |p|
+ = p.inputs :first_name, :last_name, :public_name
+ = p.input :company, :label => "Affiliation", :hint => "This could be a company, a user group, or nothing at all."
+ = p.input :biography, :input_html => {:rows => 5, "onkeyup" => "word_count(this, 'biography-count')"}, :for => :person
+ You have used
+ %span#biography-count #{current_user.person.biography_word_count}
+ words. Biographies are limited to 150 words.
+ %br
+ %br
+ = f.inputs :name => "Account" do
+ = f.input :email, :required => false
+ = f.input :password, :hint => "(Leave blank if you don't want to change it)", :input_html => {:autocomplete => "off"}
+ = f.input :password_confirmation, :input_html => {:autocomplete => "off"}
+ = f.input :current_password, :input_html => {:autocomplete => "off"}, :hint => "(we need your current password to confirm password or email changes)"
+ = f.submit "Update"
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
new file mode 100644
index 00000000..9703db37
--- /dev/null
+++ b/app/views/devise/registrations/new.html.erb
@@ -0,0 +1,18 @@
+Sign up
+
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit "Sign up" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/sessions/new.html.erb-backup b/app/views/devise/sessions/new.html.erb-backup
new file mode 100644
index 00000000..30ab9196
--- /dev/null
+++ b/app/views/devise/sessions/new.html.erb-backup
@@ -0,0 +1,18 @@
+/
+Sign in
+
+<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <% if devise_mapping.rememberable? -%>
+ <%= f.check_box :remember_me %> <%= f.label :remember_me %>
+ <% end -%>
+
+ <%= f.submit "Sign in" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
new file mode 100644
index 00000000..0974e5ed
--- /dev/null
+++ b/app/views/devise/sessions/new.html.haml
@@ -0,0 +1,13 @@
+.container
+ %h2.center-text
+ Sign in
+ .row
+ .span6.offset3
+ = semantic_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f|
+ = f.input :email
+ = f.input :password
+ - if devise_mapping.rememberable?
+ = f.input :remember_me, :as=> :boolean
+ = f.submit "Sign in"
+
+ = render "devise/shared/links"
diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb
new file mode 100644
index 00000000..eab783a4
--- /dev/null
+++ b/app/views/devise/shared/_links.erb
@@ -0,0 +1,25 @@
+<%- if controller_name != 'sessions' %>
+ <%= link_to "Sign in", new_session_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+ <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.omniauthable? %>
+ <%- resource_class.omniauth_providers.each do |provider| %>
+ <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
+ <% end -%>
+<% end -%>
\ No newline at end of file
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
new file mode 100644
index 00000000..e55e82ee
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,12 @@
+Resend unlock instructions
+
+<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend unlock instructions" %>
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
new file mode 100644
index 00000000..92a2bfda
--- /dev/null
+++ b/app/views/home/index.html.haml
@@ -0,0 +1,38 @@
+.container-fluid
+ %h1.center-text
+ Upcoming Conferences
+ - @conferences.each do |conference|
+ - if conference.cfp_open?
+ .well
+ %h2.center-text
+ = conference.title
+ %h4.center-text
+ = conference.date_range_string
+ .center-text
+ %i
+ %a= link_to conference.venue.name, conference.venue.website
+ ,
+ = conference.venue.address
+ .row-fluid{:style => "padding-top: 30px;"}
+ .span4
+ - if conference.user_registered?(current_user)
+ = link_to "Modify Registration", register_conference_path(conference.short_title), :class => "mainbutton center-text"
+ %br
+ %i.icon-thumbs-up You're attending!
+ - else
+ = link_to "Register", register_conference_path(conference.short_title), :class => "mainbutton center-text"
+ %br
+ - if !current_user.nil?
+ %i.icon-thumbs-down
+ You haven't registered yet!
+ .span4.offset4.pull-right
+ - if !current_user.nil? && current_user.person.proposal_count(conference) > 0
+ = link_to "View My Proposals", conference_proposal_index_path(conference.short_title), :class => "mainbutton center-text"
+ %br
+ - if !current_user.nil?
+ %i.icon-star
+ You've submitted
+ %b= current_user.person.proposal_count conference
+ proposals!
+ - else
+ = link_to "Submit Proposal", conference_proposal_index_path(conference.short_title), :class => "mainbutton center-text"
\ No newline at end of file
diff --git a/app/views/layouts/_admin_navigation.html.haml b/app/views/layouts/_admin_navigation.html.haml
new file mode 100644
index 00000000..418443f3
--- /dev/null
+++ b/app/views/layouts/_admin_navigation.html.haml
@@ -0,0 +1,44 @@
+- if @conference.nil? || @conference.short_title.nil?
+ = link_to "OSEM", admin_conference_index_path, :class => 'brand'
+- else
+ = link_to @conference.title, admin_conference_path(@conference.short_title), :class => 'brand'
+- if @conference && !@conference.short_title.nil?
+ %ul.nav
+ - if current_page?(admin_conference_registrations_path(@conference.short_title))
+ %li.active
+ = link_to "Registrations", "#"
+ - else
+ %li
+ = link_to "Registrations", admin_conference_registrations_path(@conference.short_title)
+ - if current_page?(admin_conference_events_path(@conference.short_title))
+ %li.active
+ = link_to "Events", "#"
+ - else
+ %li
+ = link_to "Events", admin_conference_events_path(@conference.short_title)
+%ul.nav.secondary-nav.pull-right
+ - if !@conference.nil?
+ %li.dropdown
+ = link_to "#", :class => "dropdown-toggle" do
+ Export
+
+ %ul.dropdown-menu
+ %li= link_to "JSON", "#"
+ %li= link_to "HTML", "#"
+ %li.dropdown
+ - unless Conference.count == 1 && !has_role?(current_user, "admin")
+ = link_to "#", :class => "dropdown-toggle" do
+ Conferences
+
+ %ul.dropdown-menu
+ - conferences = Conference.order("created_at DESC")
+ - unless conferences.nil?
+ - conferences.each do |c|
+ - unless @conference == c
+ %li= link_to c.short_title, admin_conference_path(c.short_title)
+ - if has_role?(current_user, "admin")
+ %li= link_to "New Conference", new_admin_conference_path
+ - if has_role?(current_user, "admin")
+ %li= link_to "Users", admin_users_path
+ %li= link_to "People", admin_people_path
+ %li= link_to "Log out", destroy_user_session_path, :method => :delete
\ No newline at end of file
diff --git a/app/views/layouts/_messages.html.haml b/app/views/layouts/_messages.html.haml
new file mode 100644
index 00000000..fd8e69a8
--- /dev/null
+++ b/app/views/layouts/_messages.html.haml
@@ -0,0 +1,5 @@
+- flash.each do |name, msg|
+ - if msg.is_a?(String)
+ %div{:class => "alert alert-#{name == :notice ? "success" : "error"}"}
+ %a.close{"data-dismiss" => "alert"} ×
+ = content_tag :div, msg, :id => "flash_#{name}"
\ No newline at end of file
diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml
new file mode 100644
index 00000000..2beda77d
--- /dev/null
+++ b/app/views/layouts/_navigation.html.haml
@@ -0,0 +1,11 @@
+= link_to "OSEM", root_path, :class => 'brand'
+%ul.nav.pull-right
+ - if user_signed_in?
+ %li
+ = link_to('Logout', destroy_user_session_path, :method=>'delete')
+ - else
+ %li
+ = link_to('Login', new_user_session_path)
+ - if user_signed_in?
+ %li
+ = link_to('Edit account', edit_user_registration_path)
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
new file mode 100644
index 00000000..a36217bb
--- /dev/null
+++ b/app/views/layouts/admin.html.haml
@@ -0,0 +1,27 @@
+!!!
+%html
+ %head
+ %meta{:charset => "utf-8"}
+ %meta{:name => "viewport", :content => "width=device-width, initial-scale=1, maximum-scale=1"}
+ %title= content_for?(:title) ? yield(:title) : "Admin"
+ %meta{:content => "", :name => "description"}
+ %meta{:content => "", :name => "author"}
+ = stylesheet_link_tag "application", :media => "all"
+ = javascript_include_tag "application"
+ = csrf_meta_tags
+ = yield(:head)
+ %body
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container-fluid
+ = render 'layouts/admin_navigation'
+ .container
+ .content
+ #wrap
+ #inner-content
+ .row-fluid
+ .span12
+ = render 'layouts/messages'
+ .row-fluid
+ .span12
+ = yield
\ No newline at end of file
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 00000000..a68290bc
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,35 @@
+!!!
+%html
+ %head
+ %meta{:charset => "utf-8"}
+ %meta{:name => "viewport", :content => "width=device-width, initial-scale=1, maximum-scale=1"}
+ %title= content_for?(:title) ? yield(:title) : "OSEM"
+ %meta{:content => "", :name => "description"}
+ %meta{:content => "", :name => "author"}
+ = stylesheet_link_tag "application", :media => "all"
+ = javascript_include_tag "application"
+ = csrf_meta_tags
+ = yield(:head)
+ %body
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container-fluid
+ = render 'layouts/navigation'
+ .container
+ .content
+ #wrap
+ #inner-content
+ .row-fluid
+ .span12
+ = render 'layouts/messages'
+ .row-fluid
+ .span12
+ = yield
+ #push
+ #footer
+ .container
+ %p.muted.credit.center-text{:style => "font-size: 10px;"}
+ OSEM is (C) 2013
+ = link_to "Matt Barringer", "http://incoherent.de"
+ and released under the GPL. You can get the source code from
+ = link_to "Github.", "https://github.com/mbarringer/osem"
\ No newline at end of file
diff --git a/app/views/proposal/_form.html.haml b/app/views/proposal/_form.html.haml
new file mode 100644
index 00000000..adb5677a
--- /dev/null
+++ b/app/views/proposal/_form.html.haml
@@ -0,0 +1,142 @@
+.tabbable
+ %ul.nav.nav-tabs
+ %li.active
+ = link_to "Proposal", "#proposal-content", "data-toggle"=>"tab"
+ %li
+ = link_to "Attachments", "#attachment-content", "data-toggle"=>"tab"
+ .tab-content
+ #proposal-content.tab-pane.active
+ .span12
+ = semantic_form_for(@event, :url => @url) do |f|
+ %section#basic
+ = f.inputs :name => "Basic Information" do
+ = f.input :title, :required => true
+ = f.input :subtitle
+ = f.input :abstract, :input_html => {:rows => 5, "onkeyup" => "word_count(this, 'abstract-count')"}, :required => true
+ You have used
+ %span#abstract-count #{@event.abstract_word_count}
+ words. Abstracts are limited to 250 words.
+ %br
+ %br
+ = f.input :description, :input_html => {:rows => 5}, :hint => "This will only be shown to organizers, not to attendees."
+ %section#details
+ = f.inputs :name => "Event Details" do
+ = f.input :event_type_id,:as => :select, :collection => @event_types, :include_blank => false
+ %section#information
+ = f.inputs :name => "Your Information" do
+ = semantic_fields_for @person do |p|
+ = p.input :public_name, :required => true
+ = p.input :company, :label => "Affiliation", :hint => "This could be a company, a user group, or nothing at all."
+ = p.input :biography, :required => true, :input_html => {:rows => 5, "onkeyup" => "word_count(this, 'biography-count')"}
+ You have used
+ %span#biography-count #{@person.biography_word_count}
+ words. Biographies are limited to 150 words.
+ %br
+ %br
+
+ %section#speakers
+ = f.inputs :name => "Additional Speakers" do
+ Will there be any additional speakers? If so, please enter their full names, email address, and a short
+ biography for each.
+ = f.input :proposal_additional_speakers, :input_html => {:rows => 5}, :label => false
+ = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"}
+ #attachment-content.tab-pane
+ .span12
+ = form_for EventAttachment.new, :url => conference_proposal_event_attachment_index_path(@conference.short_title, @event),
+ :html => { :multipart => true, :id => "fileupload" } do |f|
+ %table.table.table-striped
+ %thead
+ %th
+ %th
+ %b Filename
+ %th
+ %b Title
+ %th
+ %b Size
+ %th
+ %b Public
+ %th
+ %tbody.files
+ .row-fluid.fileupload-buttonbar
+ .btn.btn-success.fileinput-button
+ %i.icon-plus.icon-white
+ Add files...
+ = f.file_field :attachment
+ = f.hidden_field :event_id, :value => @event.id
+
+:javascript
+ $(document).ready( function() {
+ $('#fileupload').fileupload();
+ $('#fileupload').bind('fileuploadsubmit', function (e, data) {
+ var inputs = data.context.find(':input');
+ if (inputs.filter('[required][value=""]').first().focus().length) {
+ return false;
+ }
+ data.formData = inputs.serializeArray();
+ });
+
+ } );
+
+:plain
+
+
+
diff --git a/app/views/proposal/edit.html.haml b/app/views/proposal/edit.html.haml
new file mode 100644
index 00000000..42ae2e85
--- /dev/null
+++ b/app/views/proposal/edit.html.haml
@@ -0,0 +1,2 @@
+.container
+ = render 'form'
\ No newline at end of file
diff --git a/app/views/proposal/index.html.haml b/app/views/proposal/index.html.haml
new file mode 100644
index 00000000..9296f0ef
--- /dev/null
+++ b/app/views/proposal/index.html.haml
@@ -0,0 +1,18 @@
+.container-fluid
+ .row-fluid
+ .span13
+ %h2.pull-left
+ My Proposals
+ .pull-right{:style=>"margin-top:20px;"}
+ = link_to "New Proposal", new_conference_proposal_path(@conference.short_title), :class => "btn btn-primary"
+ - @events.each do |event|
+ .row-fluid
+ .span12
+ .well
+ .row-fluid
+ .span10
+ = event.title
+ .pull-right
+ = link_to "Edit", edit_conference_proposal_path(@conference.short_title, event.id), :class => "btn btn-mini btn-primary"
+ = link_to "Withdraw", conference_proposal_path(@conference.short_title, event.id), :method => :delete,
+ :confirm => "Are you sure you want to withdraw this proposal?", :class => "btn btn-mini btn-danger"
diff --git a/app/views/proposal/new.html.haml b/app/views/proposal/new.html.haml
new file mode 100644
index 00000000..42ae2e85
--- /dev/null
+++ b/app/views/proposal/new.html.haml
@@ -0,0 +1,2 @@
+.container
+ = render 'form'
\ No newline at end of file
diff --git a/app/views/shared/_dynamic_association.html.haml b/app/views/shared/_dynamic_association.html.haml
new file mode 100644
index 00000000..1484100a
--- /dev/null
+++ b/app/views/shared/_dynamic_association.html.haml
@@ -0,0 +1,10 @@
+= f.inputs title do
+ - if hint
+ %p.inline-hint= hint
+ %div{:class => association_name.to_s}
+ = f.semantic_fields_for association_name do |association_form|
+ = render association_name.to_s.singularize + "_fields", :f => association_form
+ .links
+ = add_association_link association_name, f, association_name
+ %br
+ %br
diff --git a/config.ru b/config.ru
new file mode 100644
index 00000000..988dac89
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Osem::Application
diff --git a/config/application.rb b/config/application.rb
new file mode 100644
index 00000000..76448fbb
--- /dev/null
+++ b/config/application.rb
@@ -0,0 +1,63 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+include ActionView::Helpers::NumberHelper
+
+if defined?(Bundler)
+ # If you precompile assets before deploying to production, use this line
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
+ # If you want your assets lazily compiled in production, use this line
+ # Bundler.require(:default, :assets, Rails.env)
+end
+
+module Osem
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+
+ # Enable escaping HTML in JSON.
+ config.active_support.escape_html_entities_in_json = true
+
+ # Use SQL instead of Active Record's schema dumper when creating the database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Enforce whitelist mode for mass assignment.
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
+ # parameters by using an attr_accessible or attr_protected declaration.
+ config.active_record.whitelist_attributes = true
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
+
+ # Version of your assets, change this if you want to expire all your assets
+ config.assets.version = '1.0'
+ end
+end
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 00000000..4489e586
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
diff --git a/config/database.yml b/config/database.yml
new file mode 100644
index 00000000..51a4dd45
--- /dev/null
+++ b/config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3
+#
+# Ensure the SQLite 3 gem is defined in your Gemfile
+# gem 'sqlite3'
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
diff --git a/config/deploy.rb b/config/deploy.rb
new file mode 100644
index 00000000..f3f702f9
--- /dev/null
+++ b/config/deploy.rb
@@ -0,0 +1,16 @@
+namespace :paperclip do
+
+ desc "Create a storage folder for Paperclip attachment in shared path"
+ task :create_storage do
+ run "mkdir -p #{shared_path}/storage"
+ end
+
+ desc "Link the Paperclip storage folder into the current release"
+ task :link_storage do
+ run "ln -nfs #{shared_path}/storage #{release_path}/storage"
+ end
+
+end
+
+before "deploy:setup", 'paperclip:create_storage'
+after "deploy:update_code", "paperclip:link_storage"
diff --git a/config/environment.rb b/config/environment.rb
new file mode 100644
index 00000000..d5b37271
--- /dev/null
+++ b/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Osem::Application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
new file mode 100644
index 00000000..113e6099
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,39 @@
+Osem::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL)
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
+
+ # Do not compress assets
+ config.assets.compress = false
+ config.assets.compile = false
+
+ # Expands the lines which load the assets
+ config.assets.debug = true
+ config.action_mailer.default_url_options = { :host => 'localhost:3000' }
+end
diff --git a/config/environments/production.rb b/config/environments/production.rb
new file mode 100644
index 00000000..e5a60292
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,67 @@
+Osem::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
+
+ # Compress JavaScripts and CSS
+ config.assets.compress = true
+
+ # Don't fallback to assets pipeline if a precompiled asset is missed
+ config.assets.compile = false
+
+ # Generate digests for assets URLs
+ config.assets.digest = true
+
+ # Defaults to nil and saved in location specified by config.assets.prefix
+ # config.assets.manifest = YOUR_PATH
+
+ # Specifies the header that your server uses for sending files
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Prepend all log lines with the following tags
+ # config.log_tags = [ :subdomain, :uuid ]
+
+ # Use a different logger for distributed setups
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # config.assets.precompile += %w( search.js )
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL)
+ # config.active_record.auto_explain_threshold_in_seconds = 0.5
+end
diff --git a/config/environments/test.rb b/config/environments/test.rb
new file mode 100644
index 00000000..29febc63
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,37 @@
+Osem::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Log error messages when you accidentally call methods on nil
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
new file mode 100644
index 00000000..59385cdf
--- /dev/null
+++ b/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
new file mode 100644
index 00000000..d8114c2b
--- /dev/null
+++ b/config/initializers/devise.rb
@@ -0,0 +1,232 @@
+# Use this hook to configure devise mailer, warden hooks and so forth.
+# Many of these configuration options can be set straight in your model.
+Devise.setup do |config|
+ # ==> Mailer Configuration
+ # Configure the e-mail address which will be shown in Devise::Mailer,
+ # note that it will be overwritten if you use your own mailer class with default "from" parameter.
+ config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
+
+ # Configure the class responsible to send e-mails.
+ # config.mailer = "Devise::Mailer"
+
+ # ==> ORM configuration
+ # Load and configure the ORM. Supports :active_record (default) and
+ # :mongoid (bson_ext recommended) by default. Other ORMs may be
+ # available as additional gems.
+ require 'devise/orm/active_record'
+
+ # ==> Configuration for any authentication mechanism
+ # Configure which keys are used when authenticating a user. The default is
+ # just :email. You can configure it to use [:username, :subdomain], so for
+ # authenticating a user, both parameters are required. Remember that those
+ # parameters are used only when authenticating and not when retrieving from
+ # session. If you need permissions, you should implement that in a before filter.
+ # You can also supply a hash where the value is a boolean determining whether
+ # or not authentication should be aborted when the value is not present.
+ # config.authentication_keys = [ :email ]
+
+ # Configure parameters from the request object used for authentication. Each entry
+ # given should be a request method and it will automatically be passed to the
+ # find_for_authentication method and considered in your model lookup. For instance,
+ # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
+ # The same considerations mentioned for authentication_keys also apply to request_keys.
+ # config.request_keys = []
+
+ # Configure which authentication keys should be case-insensitive.
+ # These keys will be downcased upon creating or modifying a user and when used
+ # to authenticate or find a user. Default is :email.
+ config.case_insensitive_keys = [ :email ]
+
+ # Configure which authentication keys should have whitespace stripped.
+ # These keys will have whitespace before and after removed upon creating or
+ # modifying a user and when used to authenticate or find a user. Default is :email.
+ config.strip_whitespace_keys = [ :email ]
+
+ # Tell if authentication through request.params is enabled. True by default.
+ # It can be set to an array that will enable params authentication only for the
+ # given strategies, for example, `config.params_authenticatable = [:database]` will
+ # enable it only for database (email + password) authentication.
+ # config.params_authenticatable = true
+
+ # Tell if authentication through HTTP Basic Auth is enabled. False by default.
+ # It can be set to an array that will enable http authentication only for the
+ # given strategies, for example, `config.http_authenticatable = [:token]` will
+ # enable it only for token authentication.
+ # config.http_authenticatable = false
+
+ # If http headers should be returned for AJAX requests. True by default.
+ # config.http_authenticatable_on_xhr = true
+
+ # The realm used in Http Basic Authentication. "Application" by default.
+ # config.http_authentication_realm = "Application"
+
+ # It will change confirmation, password recovery and other workflows
+ # to behave the same regardless if the e-mail provided was right or wrong.
+ # Does not affect registerable.
+ # config.paranoid = true
+
+ # By default Devise will store the user in session. You can skip storage for
+ # :http_auth and :token_auth by adding those symbols to the array below.
+ # Notice that if you are skipping storage for all authentication paths, you
+ # may want to disable generating routes to Devise's sessions controller by
+ # passing :skip => :sessions to `devise_for` in your config/routes.rb
+ config.skip_session_storage = [:http_auth]
+
+ # ==> Configuration for :database_authenticatable
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If
+ # using other encryptors, it sets how many times you want the password re-encrypted.
+ #
+ # Limiting the stretches to just one in testing will increase the performance of
+ # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
+ # a value less than 10 in other environments.
+ config.stretches = Rails.env.test? ? 1 : 10
+
+ # Setup a pepper to generate the encrypted password.
+ # config.pepper = "9a89de1df41b08b48af904bbbc5b8d713addd9d14e2758f12dd14105acd8627fececa02f33f89c0e9764e74262d8874c4a52534b788f5aa4b1716522ae6888b1"
+
+ # ==> Configuration for :confirmable
+ # A period that the user is allowed to access the website even without
+ # confirming his account. For instance, if set to 2.days, the user will be
+ # able to access the website for two days without confirming his account,
+ # access will be blocked just in the third day. Default is 0.days, meaning
+ # the user cannot access the website without confirming his account.
+ # config.allow_unconfirmed_access_for = 2.days
+
+ # If true, requires any email changes to be confirmed (exactly the same way as
+ # initial account confirmation) to be applied. Requires additional unconfirmed_email
+ # db field (see migrations). Until confirmed new email is stored in
+ # unconfirmed email column, and copied to email column on successful confirmation.
+ config.reconfirmable = true
+
+ # Defines which key will be used when confirming an account
+ # config.confirmation_keys = [ :email ]
+
+ # ==> Configuration for :rememberable
+ # The time the user will be remembered without asking for credentials again.
+ # config.remember_for = 2.weeks
+
+ # If true, extends the user's remember period when remembered via cookie.
+ # config.extend_remember_period = false
+
+ # Options to be passed to the created cookie. For instance, you can set
+ # :secure => true in order to force SSL only cookies.
+ # config.rememberable_options = {}
+
+ # ==> Configuration for :validatable
+ # Range for password length. Default is 6..128.
+ # config.password_length = 6..128
+
+ # Email regex used to validate email formats. It simply asserts that
+ # an one (and only one) @ exists in the given string. This is mainly
+ # to give user feedback and not to assert the e-mail validity.
+ # config.email_regexp = /\A[^@]+@[^@]+\z/
+
+ # ==> Configuration for :timeoutable
+ # The time you want to timeout the user session without activity. After this
+ # time the user will be asked for credentials again. Default is 30 minutes.
+ # config.timeout_in = 30.minutes
+
+ # If true, expires auth token on session timeout.
+ # config.expire_auth_token_on_timeout = false
+
+ # ==> Configuration for :lockable
+ # Defines which strategy will be used to lock an account.
+ # :failed_attempts = Locks an account after a number of failed attempts to sign in.
+ # :none = No lock strategy. You should handle locking by yourself.
+ # config.lock_strategy = :failed_attempts
+
+ # Defines which key will be used when locking and unlocking an account
+ # config.unlock_keys = [ :email ]
+
+ # Defines which strategy will be used to unlock an account.
+ # :email = Sends an unlock link to the user email
+ # :time = Re-enables login after a certain amount of time (see :unlock_in below)
+ # :both = Enables both strategies
+ # :none = No unlock strategy. You should handle unlocking by yourself.
+ # config.unlock_strategy = :both
+
+ # Number of authentication tries before locking an account if lock_strategy
+ # is failed attempts.
+ # config.maximum_attempts = 20
+
+ # Time interval to unlock the account if :time is enabled as unlock_strategy.
+ # config.unlock_in = 1.hour
+
+ # ==> Configuration for :recoverable
+ #
+ # Defines which key will be used when recovering the password for an account
+ # config.reset_password_keys = [ :email ]
+
+ # Time interval you can reset your password with a reset password key.
+ # Don't put a too small interval or your users won't have the time to
+ # change their passwords.
+ config.reset_password_within = 6.hours
+
+ # ==> Configuration for :encryptable
+ # Allow you to use another encryption algorithm besides bcrypt (default). You can use
+ # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
+ # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
+ # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
+ # REST_AUTH_SITE_KEY to pepper)
+ # config.encryptor = :sha512
+
+ # ==> Configuration for :token_authenticatable
+ # Defines name of the authentication token params key
+ # config.token_authentication_key = :auth_token
+
+ # ==> Scopes configuration
+ # Turn scoped views on. Before rendering "sessions/new", it will first check for
+ # "users/sessions/new". It's turned off by default because it's slower if you
+ # are using only default views.
+ # config.scoped_views = false
+
+ # Configure the default scope given to Warden. By default it's the first
+ # devise role declared in your routes (usually :user).
+ # config.default_scope = :user
+
+ # Set this configuration to false if you want /users/sign_out to sign out
+ # only the current scope. By default, Devise signs out all scopes.
+ # config.sign_out_all_scopes = true
+
+ # ==> Navigation configuration
+ # Lists the formats that should be treated as navigational. Formats like
+ # :html, should redirect to the sign in page when the user does not have
+ # access, but formats like :xml or :json, should return 401.
+ #
+ # If you have any extra navigational formats, like :iphone or :mobile, you
+ # should add them to the navigational formats lists.
+ #
+ # The "*/*" below is required to match Internet Explorer requests.
+ # config.navigational_formats = ["*/*", :html]
+
+ # The default HTTP method used to sign out a resource. Default is :delete.
+ config.sign_out_via = :delete
+
+ # ==> OmniAuth
+ # Add a new OmniAuth provider. Check the wiki for more information on setting
+ # up on your models and hooks.
+ # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
+
+ # ==> Warden configuration
+ # If you want to use other strategies, that are not supported by Devise, or
+ # change the failure app, you can configure them inside the config.warden block.
+ #
+ # config.warden do |manager|
+ # manager.intercept_401 = false
+ # manager.default_strategies(:scope => :user).unshift :some_external_strategy
+ # end
+
+ # ==> Mountable engine configurations
+ # When using Devise inside an engine, let's call it `MyEngine`, and this engine
+ # is mountable, there are some extra configurations to be taken into account.
+ # The following options are available, assuming the engine is mounted as:
+ #
+ # mount MyEngine, at: "/my_engine"
+ #
+ # The router that invoked `devise_for`, in the example above, would be:
+ # config.router_name = :my_engine
+ #
+ # When using omniauth, Devise cannot automatically set Omniauth path,
+ # so you need to do it manually. For the users scope, it would be:
+ # config.omniauth_path_prefix = "/my_engine/users/auth"
+end
\ No newline at end of file
diff --git a/config/initializers/formtastic.rb b/config/initializers/formtastic.rb
new file mode 100644
index 00000000..69eaae95
--- /dev/null
+++ b/config/initializers/formtastic.rb
@@ -0,0 +1,79 @@
+# encoding: utf-8
+
+# Set the default text field size when input is a string. Default is nil.
+# Formtastic::FormBuilder.default_text_field_size = 50
+
+# Set the default text area height when input is a text. Default is 20.
+# Formtastic::FormBuilder.default_text_area_height = 5
+
+# Set the default text area width when input is a text. Default is nil.
+# Formtastic::FormBuilder.default_text_area_width = 50
+
+# Should all fields be considered "required" by default?
+# Defaults to true.
+# Formtastic::FormBuilder.all_fields_required_by_default = true
+
+# Should select fields have a blank option/prompt by default?
+# Defaults to true.
+Formtastic::FormBuilder.include_blank_for_select_by_default = false
+
+# Set the string that will be appended to the labels/fieldsets which are required
+# It accepts string or procs and the default is a localized version of
+# '* '. In other words, if you configure formtastic.required
+# in your locale, it will replace the abbr title properly. But if you don't want to use
+# abbr tag, you can simply give a string as below
+Formtastic::FormBuilder.required_string = "(required)"
+
+# Set the string that will be appended to the labels/fieldsets which are optional
+# Defaults to an empty string ("") and also accepts procs (see required_string above)
+# Formtastic::FormBuilder.optional_string = "(optional)"
+
+# Set the way inline errors will be displayed.
+# Defaults to :sentence, valid options are :sentence, :list, :first and :none
+# Formtastic::FormBuilder.inline_errors = :sentence
+# Formtastic uses the following classes as default for hints, inline_errors and error list
+
+# If you override the class here, please ensure to override it in your stylesheets as well
+# Formtastic::FormBuilder.default_hint_class = "inline-hints"
+# Formtastic::FormBuilder.default_inline_error_class = "inline-errors"
+# Formtastic::FormBuilder.default_error_list_class = "errors"
+
+# Set the method to call on label text to transform or format it for human-friendly
+# reading when formtastic is used without object. Defaults to :humanize.
+# Formtastic::FormBuilder.label_str_method = :humanize
+
+# Set the array of methods to try calling on parent objects in :select and :radio inputs
+# for the text inside each @@ tag or alongside each radio @ @. The first method
+# that is found on the object will be used.
+# Defaults to ["to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
+# Formtastic::FormBuilder.collection_label_methods = [
+# "to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
+
+# Specifies if labels/hints for input fields automatically be looked up using I18n.
+# Default value: true. Overridden for specific fields by setting value to true,
+# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
+# Formtastic::FormBuilder.i18n_lookups_by_default = false
+
+# Specifies if I18n lookups of the default I18n Localizer should be cached to improve performance.
+# Defaults to true.
+# Formtastic::FormBuilder.i18n_cache_lookups = false
+
+# Specifies the class to use for localization lookups. You can create your own
+# class and use it instead by subclassing Formtastic::Localizer (which is the default).
+# Formtastic::FormBuilder.i18n_localizer = MyOwnLocalizer
+
+# You can add custom inputs or override parts of Formtastic by subclassing Formtastic::FormBuilder and
+# specifying that class here. Defaults to Formtastic::FormBuilder.
+# Formtastic::Helpers::FormHelper.builder = MyCustomBuilder
+
+# You can opt-in to Formtastic's use of the HTML5 `required` attribute on ` `, ``
+# and ` ",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="
",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML=" ",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/ ]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""," "],legend:[1,""," "],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],area:[1,""
+," "],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X ","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>$2>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/