diff --git a/.gitignore b/.gitignore index 11edcb0..74254e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,18 @@ + # Ignore bundler config /.bundle -# Ignore all logfiles, tempfiles, public assets, +# Ignore all logfiles, tempfiles, public assets, /log/*.log /tmp public/assets/* +public/apidocs/* # Ignore gemfile.lock Gemfile.lock # Ignore db schema.rb -#db/schema.rb +# db/schema.rb # Ignore database configuration and token secrets config/database.yml @@ -26,7 +28,14 @@ config/environments/production.rb config/environments/test.rb +config/initializers/contact_us.rb -# Ignore local licensing and readme information -README.rdoc +# ignore IDE files +.idea/* +# ignore yard doc files +.yardoc/* + +# ignore yard generated documents +/doc/* +!/doc/README_FOR_APP \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ff8c728 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: ruby +rvm: + - 2.1.10 + +services: + - mysql + +before_script: + - cp config/database_example.yml config/database.yml + - cp config/secrets_example.yml config/secrets.yml + - mysql -e 'create database IF NOT EXISTS roadmap_test;' -uroot + +script: + - bundle exec rake db:test:prepare + - bundle exec rake diff --git a/Gemfile b/Gemfile index 14abcde..e8bf467 100644 --- a/Gemfile +++ b/Gemfile @@ -2,17 +2,19 @@ # # RAILS # -gem 'rails', '4.0.0' +gem 'rails', '4.2.0' # additional gems for rails 4 -gem 'railties', '= 4.0.0' +gem 'railties'#, '= 4.2.0' +gem 'responders', '~> 2.0' # allows use of respond_with and respond_to in controllers + # add these gems to help with the transition: gem 'protected_attributes' gem 'rails-observers' gem 'actionpack-page_caching' gem 'actionpack-action_caching' -gem 'uglifier', '>= 1.3.0' +gem 'uglifier' #, '>= 1.3.0' # Gems used only for assets and not required in production environments by default. #group :assets do @@ -20,7 +22,7 @@ #end # Use SCSS for stylesheets gem 'sass-rails' -#gem 'sass' +#gem 'sass' # Use SCSS for stylesheets @@ -39,10 +41,14 @@ #to allow cloning of objects gem 'amoeba' +group :development, :test do + gem "byebug" +end + group :development do - gem 'web-console', '~>2.0' gem "better_errors" gem "binding_of_caller" + gem 'web-console', '~>2.0' end # @@ -62,7 +68,7 @@ # # DATABASE/SERVER # -gem 'mysql2', '~> 0.3.10' +gem 'mysql2', '~> 0.3.18' # Use unicorn as the app server # gem 'unicorn' #cancan for usergroups @@ -73,7 +79,7 @@ # gem 'jquery-rails' gem 'tinymce-rails' -gem 'friendly_id', '~> 5.0.1' +gem 'friendly_id' #, '~> 5.0.1' gem 'contact_us' gem 'recaptcha' gem 'turbolinks' @@ -91,4 +97,33 @@ gem 'caracal' gem 'caracal-rails' +# +# INTERNATIONALIZATION +# +gem "i18n-js", ">= 3.0.0.rc11" #damodar added TODO: explain + +# +# TESTING +# +group :test do + gem 'minitest-rails-capybara' + gem 'minitest-reporters' + gem 'rack-test' +end + +# +# API +# +gem 'swagger-docs' + +# +# CODE DOCUMENTATION +# +gem 'yard' +gem 'redcarpet' + +# +# API +# +gem 'swagger-docs' diff --git a/LICENSE.md b/LICENSE.md index 2898f15..ba4db2c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 University of Edinburgh & The Regents of the University of California +Copyright (c) 2016 University of Edinburgh, University of Glasgow & The Regents of the University of California Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/README.md b/README.md index cebdc9a..67fcb25 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ #### Current Release v.0.1.0 +[![Build Status](https://travis-ci.org/DMPRoadmap/roadmap.svg)](https://travis-ci.org/DMPRoadmap/roadmap) #### Summary diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..9a680a0 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,36 @@ += DMPRoadmap + +Roadmap is a data management planning tool, available at https://github.com/DMPRoadmap/roadmap + +Development of the Roadmap is provided by the Digital Curation Centre and the University of California Curation Center. + +The tool has four main functions +1. To help create and maintain different versions of Data Management Plans; +2. To provide useful guidance on data management issues and how to meet research funders' requirements; +3. To export attractive and useful plans in a variety of formats; +4. To allow collaborative work when creating Data Management Plans. + +== Documentation & Support + +* You can contact us by email, roadmap-l@listserv.ucop.edu, but we can only provide limited support for your installation + +== Bugs & Feature Requests + +* Bug Reports & Feature Requests: https://github.com/DMPRoadmap/roadmap/issues +* Please prefix your request with either: 'Bug:' or 'Feature:' + +== Prerequisites + +Roadmap is a Ruby on Rails application and you will need to have Ruby 2.0.0p247 or greater installed on your server and a MySQL server v5.0 or greater. + +Further details on how to install Ruby on Rails applications are available from the Ruby on Rails site, http://rubyonrails.org + +You may also find the following resources handy: + +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Copyright + +The Roadmap project uses an MIT License. The full text of the license can be found at: https://github.com/DMPRoadmap/roadmap/blob/master/LICENSE.md diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 04c8372..0d74091 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,13 @@ #!/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 'rake/testtask' require File.expand_path('../config/application', __FILE__) DMPonline4::Application.load_tasks +# TODO: destroy rdoc rake task once finished with new documentation + RDoc::Task.new :rdoc do |rdoc| rdoc.main = "README.rdoc" @@ -14,4 +16,6 @@ rdoc.title = "DMPonline4 Documentation" rdoc.options << "--all" -end \ No newline at end of file +end + +task default: :test diff --git a/app/admin/dmptemplate.rb b/app/admin/dmptemplate.rb index e3f39b0..c172563 100644 --- a/app/admin/dmptemplate.rb +++ b/app/admin/dmptemplate.rb @@ -30,7 +30,7 @@ end if settings.save - redirect_to(action: :show, flash: { notice: 'Settings updated successfully' }) + redirect_to(action: :show, flash: { notice: I18n.t('admin.settings_updated') }) else settings.formatting = nil @template = resource @@ -39,7 +39,8 @@ end end - action_item only: %i( show edit ) do + #action_item only: %i( show edit ) do + action_item(:edit) do link_to(I18n.t('helpers.settings.title'), settings_admin_dmptemplate_path(resource.id)) end diff --git a/app/admin/guidance.rb b/app/admin/guidance.rb index fbcc315..753fd07 100644 --- a/app/admin/guidance.rb +++ b/app/admin/guidance.rb @@ -61,12 +61,12 @@ end f.inputs "Themes" do - f.input :theme_ids, :label => "Selected themes", + f.input :theme_ids, :label => I18n.t('admin.selected_themes'), :as => :select, - :include_blank => "All themes", + :include_blank => I18n.t('admin.all_themes'), :multiple => true, :collection => Theme.order('title').map{|the| [the.title, the.id]}, - :hint => 'Choose all themes that apply.' + :hint => I18n.t('admin.choose_themes') end f.actions diff --git a/app/admin/guidance_group.rb b/app/admin/guidance_group.rb index 839be8a..5a54831 100644 --- a/app/admin/guidance_group.rb +++ b/app/admin/guidance_group.rb @@ -62,12 +62,12 @@ end f.inputs "Templates" do - f.input :dmptemplate_ids, :label => "Selected templates", + f.input :dmptemplate_ids, :label => I18n.t('admin.selected_templates'), :as => :select, - :include_blank => "All Templates", + :include_blank => I18n.t('admin.all_templates'), :multiple => true, :collection => Dmptemplate.order('title').map{|the| [the.title, the.id]}, - :hint => 'Choose all templates that apply.' + :hint => I18n.t('admin.choose_templates') end f.actions end diff --git a/app/admin/org_token_permission.rb b/app/admin/org_token_permission.rb new file mode 100644 index 0000000..e335d7f --- /dev/null +++ b/app/admin/org_token_permission.rb @@ -0,0 +1,31 @@ +ActiveAdmin.register OrgTokenPermission do + permit_params :organisation_id, :token_permission_type_id + + menu priority: 40, label: proc{ I18n.t('admin.org_token_permission')}, parent: "Api" + + index do + column I18n.t('admin.org') do |n| + link_to n.organisation, [:admin,n] + end + column I18n.t('admin.token_permission') do |n| + link_to n.token_permission_type, [:admin,n] + end + + actions + end + + show do + attributes_table do + row :organisation_id + row :token_permission_type_id + end + end + + controller do + def permitted_params + params.permit! + end + end + + +end diff --git a/app/admin/organisation.rb b/app/admin/organisation.rb index 8b4afbc..2c88380 100644 --- a/app/admin/organisation.rb +++ b/app/admin/organisation.rb @@ -1,15 +1,15 @@ # [+Project:+] DMPonline -# [+Description:+] -# +# [+Description:+] +# # [+Created:+] 03/09/2014 -# [+Copyright:+] Digital Curation Centre +# [+Copyright:+] Digital Curation Centre ActiveAdmin.register Organisation do permit_params :abbreviation, :banner_file_id, :description, :domain, :logo_file_id, :name, :stylesheet_file_id, :target_url, :organisation_type_id, :wayfless_entity, :parent_id - + menu :priority => 14, :label => proc{I18n.t('admin.org')}, :parent => "Organisations management" - index do + index do column I18n.t('admin.org_title'), :sortable => :name do |ggn| link_to ggn.name, [:admin, ggn] end @@ -23,15 +23,16 @@ column I18n.t('admin.org_type'), :sortable => :organisation_type_id do |org_type| if !org_type.organisation_type_id.nil? then link_to org_type.organisation_type.name, [:admin, org_type] - end + end end - + actions end - - + + #show details of an organisation - show do + show do + resource.check_api_credentials attributes_table do row I18n.t('admin.org_title'), :sortable => :name do |gn| if !gn.name.nil? then @@ -45,12 +46,12 @@ '-' end end - row :sort_name + row :sort_name row I18n.t('admin.org_type'), :organisation_type_id do |org_type| if !org_type.organisation_type_id.nil? then link_to org_type.organisation_type.name, [:admin, org_type] - end - end + end + end row :description do |descr| if !descr.description.nil? then descr.description.html_safe @@ -65,18 +66,21 @@ row :logo_file_name row :domain row :wayfless_entity + row I18n.t('admin.token_permission_type') do + (organisation.token_permission_types.map{|tpt| link_to tpt.token_type, [:admin, tpt]}).join(', ').html_safe + end # row I18n.t('admin.org_parent'), :parent_id do |org_parent| # if !org_parent.parent_id.nil? then # parent_org = Organisation.find(org_parent.parent_id) # link_to parent_org.name, [:admin, parent_org] - # end + # end # end # row :stylesheet_file_id row :created_at row :updated_at end - end - + end + #templates sidebar sidebar I18n.t('admin.templates'), :only => :show, :if => proc { organisation.dmptemplates.count >= 1} do table_for organisation.dmptemplates.order("title asc") do |temp| @@ -87,7 +91,7 @@ end end - #form + #form form do |f| f.inputs "Details" do f.input :name @@ -100,18 +104,22 @@ f.input :logo_file_name f.input :domain f.input :wayfless_entity + f.input :token_permission_types, label: I18n.t('admin.token_permission_type'), + as: :select, multiple: true, include_blank: I18n.t('helpers.none'), + collection: TokenPermissionType.order(:token_type).map{|token| [token.token_type, token.id]}, + hint: I18n.t('admin.choose_api_permissions') # f.input :parent_id, :label => I18n.t('admin.org_parent'), :as => :select, :collection => Organisation.find(:all, :order => 'name ASC').map{|orgp|[orgp.name, orgp.id]} # f.input :stylesheet_file_id end - f.actions - end + f.actions + end controller do def permitted_params params.permit! end - end - - + end + + end diff --git a/app/admin/question.rb b/app/admin/question.rb index 6d70ccd..f301e60 100644 --- a/app/admin/question.rb +++ b/app/admin/question.rb @@ -106,17 +106,17 @@ end f.inputs "Question Format" do - f.input :question_format_id, :label => "Select question format", + f.input :question_format_id, :label => I18n.t('admin.select_question_format'), :as => :select, - :collection => QuestionFormat.order('title').map{|format| [format.title, format.id]} + :collection => QuestionFormat.order('title').map{|format| [format.title, format.id]} end f.inputs "Themes" do - f.input :theme_ids, :label => "Selected themes", + f.input :theme_ids, :label => I18n.t('admin.selected_themes'), :as => :select, :multiple => true, - :include_blank => "None", + :include_blank => I18n.t('helpers.none'), :collection => Theme.order('title').map{|the| [the.title, the.id]} , - :hint => 'Choose all themes that apply.' + :hint => I18n.t('admin.choose_themes') end f.actions diff --git a/app/admin/theme.rb b/app/admin/theme.rb index de9b562..5122921 100644 --- a/app/admin/theme.rb +++ b/app/admin/theme.rb @@ -41,16 +41,16 @@ if !question.section.version.phase.dmptemplate.nil? then link_to question.section.version.phase.dmptemplate.title, [:admin, question.section.version.phase.dmptemplate] else - 'No template' + I18n.t('admin.no_template') end else - 'No phase' + I18n.t('admin.no_phase') end else - 'No version' + I18n.t('admin.no_version') end else - 'No section' + I18n.t('admin.no_section') end } end diff --git a/app/admin/token_permission_type.rb b/app/admin/token_permission_type.rb new file mode 100644 index 0000000..de4aa2d --- /dev/null +++ b/app/admin/token_permission_type.rb @@ -0,0 +1,33 @@ +ActiveAdmin.register TokenPermissionType do + permit_params :token_type, :text_desription + + menu priority: 40, label: proc{ I18n.t('admin.token_permission_type')}, parent: "Api" + + # TODO: Find better fix for the undefined method xxx_id_eq + remove_filter :org_token_permissions + + index do + column I18n.t('admin.token_permission_type'), sortable: :token_type do |n| + link_to n.token_type, [:admin, n] + end + column I18n.t('admin.permission_description') do |n| + link_to n.text_desription, [:admin, n] + end + + actions + end + + show do + attributes_table do + row :token_type + row :text_desription + end + end + + controller do + def permitted_params + params.permit! + end + end + +end diff --git a/app/admin/user.rb b/app/admin/user.rb index 8fd09cf..450472f 100644 --- a/app/admin/user.rb +++ b/app/admin/user.rb @@ -1,25 +1,25 @@ # [+Project:+] DMPonline -# [+Description:+] -# +# [+Description:+] +# # [+Created:+] 03/09/2014 -# [+Copyright:+] Digital Curation Centre +# [+Copyright:+] Digital Curation Centre -ActiveAdmin.register User do - permit_params :password_confirmation, :encrypted_password, :remember_me, :id, :email, :firstname, :orcid_id, :shibboleth_id, :user_status_id, :surname, :user_type_id, :organisation_id, :skip_invitation, :other_organisation, :accept_terms, :role_ids - +ActiveAdmin.register User do + permit_params :api_token, :password_confirmation, :encrypted_password, :remember_me, :id, :email, :firstname, :orcid_id, :shibboleth_id, :user_status_id, :surname, :user_type_id, :organisation_id, :skip_invitation, :other_organisation, :accept_terms, :role_ids + menu :priority => 15, :label => proc{ I18n.t('admin.user')}, :parent => "User management" - + filter :firstname filter :surname filter :email filter :organisations - filter :other_organisation + filter :other_organisation filter :created_at filter :updated_at - - - - index do + + + + index do column I18n.t('admin.user_name'), :sortable => :email do |user_email| link_to user_email.email, [:admin, user_email] @@ -30,21 +30,21 @@ column I18n.t('admin.surname'), :sortable => :surname do |user| link_to user.surname, [:admin, user] end - column I18n.t('admin.last_logged_in'), :last_sign_in_at + column I18n.t('admin.last_logged_in'), :last_sign_in_at column I18n.t('admin.org_title'), :sortable => 'organisations.name' do |org_title| if !org_title.organisation.nil? then if org_title.other_organisation.nil? || org_title.other_organisation == "" then link_to org_title.organisation.name, [:admin, org_title.organisation] else I18n.t('helpers.org_type.org_name') + ' - ' + org_title.other_organisation - - end + + end end end - + actions end - + show do attributes_table do row :firstname @@ -53,72 +53,75 @@ row :orcid_id row I18n.t('admin.org_title'), :organisation_id do |org_title| if !org_title.organisation_id.nil? then - link_to org_title.organisation.name, [:admin, org_title.organisation] - end + link_to org_title.organisation.name, [:admin, org_title.organisation] + end end - row :other_organisation + row :other_organisation # row I18n.t('admin.user_status'), :user_status_id do |us| # if !us.user_status.nil? then # link_to us.user_status.name, [:admin, us.user_status] - # end + # end # end # row I18n.t('admin.user_type'), :user_type_id do |ut| # if !ut.user_type.nil? then # link_to ut.user_type.name, [:admin, ut.user_type] # else # '-' - # end + # end # end - row I18n.t('admin.user_role') do + row I18n.t('admin.user_role') do (user.roles.map{|ro| link_to ro.name, [:admin, ro]}).join(', ').html_safe end # row :shibboleth_id row :last_sign_in_at - row :sign_in_count + row :sign_in_count + row :api_token end end - - + + form do |f| f.inputs "Details" do f.input :firstname f.input :surname f.input :email f.input :orcid_id + f.input :api_token # f.input :shibboleth_id - f.input :organisation_id ,:label => I18n.t('admin.org_title'), - :as => :select, + f.input :organisation_id ,:label => I18n.t('admin.org_title'), + :as => :select, :collection => Organisation.order('name').map{|orgp|[orgp.name, orgp.id]} - f.input :other_organisation - # f.input :user_status_id, :label => I18n.t('admin.user_status'), - # :as => :select, + f.input :other_organisation + # f.input :user_status_id, :label => I18n.t('admin.user_status'), + # :as => :select, # :collection => UserStatus.find(:all, :order => 'name ASC').map{|us|[us.name, us.id]} - # f.input :user_type_id, :label => I18n.t('admin.user_type'), - # :as => :select, - # :collection => UserType.find(:all, :order => 'name ASC').map{|ut|[ut.name, ut.id]} - f.input :role_ids, :label => "User role", - :as => :select, + # f.input :user_type_id, :label => I18n.t('admin.user_type'), + # :as => :select, + # :collection => UserType.find(:all, :order => 'name ASC').map{|ut|[ut.name, ut.id]} + f.input :role_ids, :label => I18n.t('admin.user_role'), + :as => :select, :multiple => true, - :include_blank => 'None', + :include_blank => I18n.t('helpers.none'), :collection => Role.order('name').map{|ro| [ro.name, ro.id]} - + + f.input :api_token end - - f.actions + + f.actions end - - - + + + controller do def scoped_collection resource_class.includes(:organisations) # prevents N+1 queries to your database end - + def permitted_params params.permit! end - + end - + end diff --git a/app/assets/images/bournemouth_logo.png b/app/assets/images/bournemouth_logo.png new file mode 100644 index 0000000..3a4ecc2 --- /dev/null +++ b/app/assets/images/bournemouth_logo.png Binary files differ diff --git a/app/assets/images/salford_logo.png b/app/assets/images/salford_logo.png new file mode 100644 index 0000000..e58c553 --- /dev/null +++ b/app/assets/images/salford_logo.png Binary files differ diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 51ff7d2..0b5dcdd 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -179,20 +179,24 @@ //action for show or hide template editing display - $('#edit_template_button').click(function(){ + $('#edit_template_button').click(function(e){ + e.preventDefault(); + $('#edit_template_div').show(); $('#show_template_div').hide(); }); //action for show or hide phase display - $('#edit_phase_button').click(function(){ + $('#edit_phase_button').click(function(e){ + e.preventDefault(); $('#edit_phase_div').show(); $('#show_phase_div').hide(); }); //action to hide the alert to edit a version - $("#edit-version-confirmed").click(function (){ + $("#edit-version-confirmed").click(function (e){ + e.preventDefault(); $("#version_edit_alert").modal("hide"); }); @@ -230,7 +234,7 @@ $('.new_question_save_button').click(function(e){ var s_id = $(this).prev(".section_id").val(); if ($('#new_question_text_'+ s_id).val() == ''){ - alert('Question text is empty, please enter your question.'); + alert(I18n.t("js.question_text_empty")); return false; } }); @@ -323,24 +327,24 @@ //verify if text area is not nil var editorContent = tinyMCE.get('guidance-text').getContent(); if (editorContent == ''){ - alert_message.push("add guidance text"); + alert_message.push(I18n.t("js.add_guidance_text")); } //verify dropdown with questions has a selected option if guidance for a question being used if ($('#g_options').val() == '2') { if ($('#questions_select').val() == '' || isNaN($('#questions_select').val())){ - alert_message.push("select a question"); + alert_message.push(I18n.t("js.select_question")); } } //verify dropdown with questions has a selected option if guidance for a question being used if ($('#g_options').val() == '1' ){ if($('#guidance_theme_ids').val() == undefined || $('#guidance_theme_ids').val() == ''){ - alert_message.push("select at least one theme"); + alert_message.push(I18n.t("js.select_at_least_one_theme")); } } //verify if guidance group is selected if ( ($('#guidance_guidance_group_ids').val() == '') || $('#guidance_guidance_group_ids').val() == undefined ) { - alert_message.push("select a guidance group"); + alert_message.push(I18n.t("js.select_guidance_group")); } if(alert_message.length == 0){ //clear dropdowns before submission @@ -385,23 +389,23 @@ //verify if text area is not nil var editorContent = tinyMCE.get('guidance-text').getContent(); if (editorContent == ''){ - alert_message.push("add guidance text"); + alert_message.push(I18n.t("js.add_guidance_text")); } //verify dropdown with questions has a selected option if guidance for a question being used if ($('#g_options').val() == '2') { if ($('#questions_select').val() == '' || isNaN($('#questions_select').val())){ - alert_message.push("select a question"); + alert_message.push(I18n.t("js.select_question")); } } //verify dropdown with questions has a selected option if guidance for a question being used if ($('#g_options').val() == '1' ){ if($('#guidance_theme_ids').val() == undefined || $('#guidance_theme_ids').val() == ''){ - alert_message.push("select at least one theme"); + alert_message.push(I18n.t("js.select_at_least_one_theme")); } } //verify if guidance group is selected if ( ($('#guidance_guidance_group_ids').val() == '') || $('#guidance_guidance_group_ids').val() == undefined ) { - alert_message.push("select a guidance group"); + alert_message.push(I18n.t("js.select_guidance_group")); } if(alert_message.length == 0){ @@ -432,12 +436,10 @@ //Validate banner_text area for less than 165 character $("form#edit_org_details").submit(function(){ - if (getStats('org_banner_text').chars > 165) { - alert("Please only enter up to 165 characters, you have used "+getStats('org_banner_text').chars+". If you are entering an URL try to use something like http://tinyurl.com/ to make it smaller."); + alert(I18n.t("js.enter_up_to") + " " + getStats('org_banner_text').chars + ". " + I18n.t("js.if_using_url_try")); return false; } - }); @@ -452,18 +454,15 @@ $(link).closest(".options_content").hide(); } - - function add_object(link, association, content) { var new_id = new Date().getTime(); - var regexp = new RegExp("new_" + association, "g") + var regexp = new RegExp("new_" + association, "g"); if (association == 'options') { $(link).parent().children('.options_table').children('.options_tbody').children('.new_option_before').before(content.replace(regexp, new_id)); } } - // Returns text statistics for the specified editor by id function getStats(id) { var body = tinymce.get(id).getBody(), text = tinymce.trim(body.innerText || body.textContent); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 9761535..0a76459 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,12 +13,12 @@ //= require jquery //= require jquery_ujs //= require twitter/bootstrap -//= require bootstrap //= require v1.js //= require select2.min.js //= require jquery.placeholder.js -//= require turbolinks - +//= require tinymce-jquery +//= require i18n +//= require i18n/translations $( document ).ready(function() { @@ -57,11 +57,13 @@ $(".help").popover(); $('.has-tooltip').tooltip({ - placement: "right", - trigger: "focus" + placement: "right", + trigger: "focus" }); - $(".show-edit-toggle").click(function () { + $(".show-edit-toggle").click(function (e) { + e.preventDefault(); + $(".edit-project").toggle(); $(".view-project").toggle(); }); @@ -135,6 +137,43 @@ }); + //Question Options + // ------------------------------------------------------------------------------------ + $(".options_table").on("click", ".remove-option", function(e){ + e.preventDefault(); + + // Mark the option for removal + $($(this).siblings()[0]).val(true); + + // Hide the entire table row and the associated hidden field for the item + $(this).parent().parent().addClass('hidden'); + }); + + $(".add-option").click(function(e){ + e.preventDefault(); + + var tbl = $(this).parent().find("table.options_table > tbody.options_tbody"), + last = tbl.find("tr:last"), + clone = last.clone(); + nbr = parseInt(last.find(".number_field").val()); + + // Update the input field names and ids + clone.find("input").each(function(index){ + $(this).prop("id", $(this).prop("id").replace(/_\d+_/g, "_" + nbr + "_")); + $(this).prop("name", $(this).prop("name").replace(/\[\d+\]/g, "[" + nbr + "]")); + }); + + // Remove the hidden class and make sure the new row is not marked for removal + clone.removeClass('hidden'); + clone.find("[id$=" + nbr + "__destroy]").val(false); + + // Default the other values + clone.find("[id$=" + nbr + "_number]").val("" + (nbr + 1)); + clone.find("[id$=" + nbr + "_text]").val(""); + clone.find("[id$=" + nbr + "_is_default]").prop("checked", false); + + last.after(clone); + }); /*$('#continue-to-new').click(function(e){ var destination = $(this).attr("href"); diff --git a/app/assets/javascripts/bootstrap.js b/app/assets/javascripts/bootstrap.js deleted file mode 100644 index 643e71c..0000000 --- a/app/assets/javascripts/bootstrap.js +++ /dev/null @@ -1,2280 +0,0 @@ -/* =================================================== - * bootstrap-transition.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#transitions - * =================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) - * ======================================================= */ - - $(function () { - - $.support.transition = (function () { - - var transitionEnd = (function () { - - var el = document.createElement('bootstrap') - , transEndEventNames = { - 'WebkitTransition' : 'webkitTransitionEnd' - , 'MozTransition' : 'transitionend' - , 'OTransition' : 'oTransitionEnd otransitionend' - , 'transition' : 'transitionend' - } - , name - - for (name in transEndEventNames){ - if (el.style[name] !== undefined) { - return transEndEventNames[name] - } - } - - }()) - - return transitionEnd && { - end: transitionEnd - } - - })() - - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-alert.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#alerts - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* ALERT CLASS DEFINITION - * ====================== */ - - var dismiss = '[data-dismiss="alert"]' - , Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - - e && e.preventDefault() - - $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) - - $parent.trigger(e = $.Event('close')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent - .trigger('closed') - .remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent.on($.support.transition.end, removeElement) : - removeElement() - } - - - /* ALERT PLUGIN DEFINITION - * ======================= */ - - var old = $.fn.alert - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('alert') - if (!data) $this.data('alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - /* ALERT NO CONFLICT - * ================= */ - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - /* ALERT DATA-API - * ============== */ - - $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) - -}(window.jQuery);/* ============================================================ - * bootstrap-button.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#buttons - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* BUTTON PUBLIC CLASS DEFINITION - * ============================== */ - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.button.defaults, options) - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - , $el = this.$element - , data = $el.data() - , val = $el.is('input') ? 'val' : 'html' - - state = state + 'Text' - data.resetText || $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d) - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.closest('[data-toggle="buttons-radio"]') - - $parent && $parent - .find('.active') - .removeClass('active') - - this.$element.toggleClass('active') - } - - - /* BUTTON PLUGIN DEFINITION - * ======================== */ - - var old = $.fn.button - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('button') - , options = typeof option == 'object' && option - if (!data) $this.data('button', (data = new Button(this, options))) - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.defaults = { - loadingText: 'loading...' - } - - $.fn.button.Constructor = Button - - - /* BUTTON NO CONFLICT - * ================== */ - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - /* BUTTON DATA-API - * =============== */ - - $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-carousel.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#carousel - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CAROUSEL CLASS DEFINITION - * ========================= */ - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.prototype = { - - cycle: function (e) { - if (!e) this.paused = false - if (this.interval) clearInterval(this.interval); - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - return this - } - - , getActiveIndex: function () { - this.$active = this.$element.find('.item.active') - this.$items = this.$active.parent().children() - return this.$items.index(this.$active) - } - - , to: function (pos) { - var activeIndex = this.getActiveIndex() - , that = this - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) { - return this.$element.one('slid', function () { - that.to(pos) - }) - } - - if (activeIndex == pos) { - return this.pause().cycle() - } - - return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) - } - - , pause: function (e) { - if (!e) this.paused = true - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - clearInterval(this.interval) - this.interval = null - return this - } - - , next: function () { - if (this.sliding) return - return this.slide('next') - } - - , prev: function () { - if (this.sliding) return - return this.slide('prev') - } - - , slide: function (type, next) { - var $active = this.$element.find('.item.active') - , $next = next || $active[type]() - , isCycling = this.interval - , direction = type == 'next' ? 'left' : 'right' - , fallback = type == 'next' ? 'first' : 'last' - , that = this - , e - - this.sliding = true - - isCycling && this.pause() - - $next = $next.length ? $next : this.$element.find('.item')[fallback]() - - e = $.Event('slide', { - relatedTarget: $next[0] - , direction: direction - }) - - if ($next.hasClass('active')) return - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - this.$element.one('slid', function () { - var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) - $nextIndicator && $nextIndicator.addClass('active') - }) - } - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - this.$element.one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) - }) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid') - } - - isCycling && this.cycle() - - return this - } - - } - - - /* CAROUSEL PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.carousel - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('carousel') - , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) - , action = typeof option == 'string' ? option : options.slide - if (!data) $this.data('carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - $.fn.carousel.defaults = { - interval: 5000 - , pause: 'hover' - } - - $.fn.carousel.Constructor = Carousel - - - /* CAROUSEL NO CONFLICT - * ==================== */ - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - /* CAROUSEL DATA-API - * ================= */ - - $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { - var $this = $(this), href - , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - , options = $.extend({}, $target.data(), $this.data()) - , slideIndex - - $target.carousel(options) - - if (slideIndex = $this.attr('data-slide-to')) { - $target.data('carousel').pause().to(slideIndex).cycle() - } - - e.preventDefault() - }) - -}(window.jQuery);/* ============================================================= - * bootstrap-collapse.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#collapse - * ============================================================= - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* COLLAPSE PUBLIC CLASS DEFINITION - * ================================ */ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.collapse.defaults, options) - - if (this.options.parent) { - this.$parent = $(this.options.parent) - } - - this.options.toggle && this.toggle() - } - - Collapse.prototype = { - - constructor: Collapse - - , dimension: function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - , show: function () { - var dimension - , scroll - , actives - , hasData - - if (this.transitioning || this.$element.hasClass('in')) return - - dimension = this.dimension() - scroll = $.camelCase(['scroll', dimension].join('-')) - actives = this.$parent && this.$parent.find('> .accordion-group > .in') - - if (actives && actives.length) { - hasData = actives.data('collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('collapse', null) - } - - this.$element[dimension](0) - this.transition('addClass', $.Event('show'), 'shown') - $.support.transition && this.$element[dimension](this.$element[0][scroll]) - } - - , hide: function () { - var dimension - if (this.transitioning || !this.$element.hasClass('in')) return - dimension = this.dimension() - this.reset(this.$element[dimension]()) - this.transition('removeClass', $.Event('hide'), 'hidden') - this.$element[dimension](0) - } - - , reset: function (size) { - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - [dimension](size || 'auto') - [0].offsetWidth - - this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') - - return this - } - - , transition: function (method, startEvent, completeEvent) { - var that = this - , complete = function () { - if (startEvent.type == 'show') that.reset() - that.transitioning = 0 - that.$element.trigger(completeEvent) - } - - this.$element.trigger(startEvent) - - if (startEvent.isDefaultPrevented()) return - - this.transitioning = 1 - - this.$element[method]('in') - - $.support.transition && this.$element.hasClass('collapse') ? - this.$element.one($.support.transition.end, complete) : - complete() - } - - , toggle: function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* COLLAPSE PLUGIN DEFINITION - * ========================== */ - - var old = $.fn.collapse - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('collapse') - , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) - if (!data) $this.data('collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.defaults = { - toggle: true - } - - $.fn.collapse.Constructor = Collapse - - - /* COLLAPSE NO CONFLICT - * ==================== */ - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - /* COLLAPSE DATA-API - * ================= */ - - $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - , target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - , option = $(target).data('collapse') ? 'toggle' : $this.data() - $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - $(target).collapse(option) - }) - -}(window.jQuery);/* ============================================================ - * bootstrap-dropdown.js v2.3.2 - * http://twitter.github.com/bootstrap/javascript.html#dropdowns - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle=dropdown]' - , Dropdown = function (element) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function (e) { - var $this = $(this) - , $parent - , isActive - - if ($this.is('.disabled, :disabled')) return - - $parent = getParent($this) - - isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement) { - // if mobile we we use a backdrop because click events don't delegate - $('