diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index 03ea9dc..92d1218 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -12,11 +12,11 @@
<%= f.label(:password, _('New password'), class: 'control-label') %> - <%= f.password_field(:password, class: 'form-control', "aria-required": true, "data-validation": "password") %> + <%= f.password_field(:password, class: 'form-control', "aria-required": true) %>
<%= f.label(:password_confirmation, _('Password confirmation'), class: 'control-label') %> - <%= f.password_field(:password_confirmation, class: 'form-control', "aria-required": true, "data-validation": "password") %> + <%= f.password_field(:password_confirmation, class: 'form-control', "aria-required": true) %>
- - <%= render :partial => 'shared/register_form', locals: {extended: true} %> + <%= render :partial => 'shared/create_account_form', locals: {extended: false} %>
diff --git a/app/views/layouts/_es5_scripts.html.erb b/app/views/layouts/_es5_scripts.html.erb index 20c99c3..e5c34ba 100644 --- a/app/views/layouts/_es5_scripts.html.erb +++ b/app/views/layouts/_es5_scripts.html.erb @@ -9,7 +9,6 @@ <%= javascript_include_tag 'jquery.tablesorter.min.js' %> <%= javascript_include_tag 'jquery.tablesorter.widgets.min.js' %> <%= javascript_include_tag 'jquery.timeago.js' %> - <%= javascript_include_tag 'tinymce.min.js' %> <%= javascript_include_tag 'bootstrap.min.js' %> @@ -46,7 +45,6 @@ <%= javascript_include_tag 'views/plans/index.js' %> <%= javascript_include_tag 'views/plans/new.js' %> <%= javascript_include_tag 'views/plans/share.js' %> - <%= javascript_include_tag 'views/plans/show.js' %> <%= javascript_include_tag 'views/registrations/sign_in_sign_up.js' %> <%#= javascript_include_tag 'views/shared/accessible_combobox.js' %> <%#= javascript_include_tag 'views/shared/accessible_submit_button.js' %> diff --git a/app/views/plans/_edit_details.html.erb b/app/views/plans/_edit_details.html.erb index 7380802..4da60a0 100644 --- a/app/views/plans/_edit_details.html.erb +++ b/app/views/plans/_edit_details.html.erb @@ -1,132 +1,208 @@ -<%= form_for plan, html: {method: :put, class: "roadmap-form"} do |f| %> -
-
-
-
- <%= f.label "#{_('Project Title')}", for: :title, class: 'required' %> - <%= f.text_field :title, class: "input-large left-indent required", 'data-toggle': "tooltip", - 'data-content': _('If applying for funding, state the name exactly as in the grant proposal.') %> - -
- <%= f.hidden_field :visibility %> - /> - -
-
- -
- <%= f.label "#{_('Funder')}", for: :funder_name %> - <%= f.text_field :funder_name, class: "input-large left-indent" %> -
- -
- <%= f.label :grant_number %> - <%= f.text_field :grant_number, class: 'input-small left-indent', 'data-toggle': "tooltip", - 'data-content': _('Grant reference number if applicable [POST-AWARD DMPs ONLY]') %> -
- -
- <%= f.label _('Project Abstract'), for: :description %> - <%= f.text_area :description, class: "left-indent" %> - - -
- -
- <%= f.label _('ID'), for: :identifier %> - <%= f.text_field :identifier, class: 'input-small left-indent', 'data-toggle': "tooltip", - 'data-content': _('A pertinent ID as determined by the funder and/or institution.') %> -
- -
- -
- -
- -
- <%= f.label _('Name'), for: :principal_investigator, 'aria-label': _('Principal Investigator Name') %> - <%= f.text_field :principal_investigator, class: 'input-small left-indent' %> -
- -
- <%= f.label _('ORCID iD'), for: :principal_investigator_identifier, 'aria-label': _('Principal Investigator ORCID iD') %> - <%= f.text_field :principal_investigator_identifier, class: 'input-small left-indent' %> -
- -
- <%= f.label _('Email'), for: :principal_investigator_email, 'aria-label': _('Principal Investigator Email') %> - <%= f.email_field :principal_investigator_email, class: 'input-small left-indent' %> - -
- -
-
- <%= f.label(:data_contact, raw("#{_('Data Contact Person')}")) %> -
-
- /> - -
-
- <%= f.label _('Name'), for: :data_contact, 'aria-label': _('Data Contact Name') %> - <%= f.text_field :data_contact, class: 'input-small left-indent' %> -
-
- <%= f.label _('Phone'), for: :data_contact_phone, 'aria-label': _('Data Contact Phone') %> - <%= f.telephone_field :data_contact_phone, class: 'input-small left-indent' %> -
-
- <%= f.label _('Email'), for: :data_contact_email, 'aria-label': _('Data Contact Email') %> - <%= f.email_field :data_contact_email, class: 'input-small left-indent' %> - -
- -
-
-
- <%= render partial: 'shared/accessible_submit_button', - locals: {id: 'save-details-button', - val: _('Save'), - disabled_initially: true, - classes: 'small-input-button left-indent', - tooltip: _('You must provide a project title and any email addresses must be valid.')} %> -
-
-
- -
-
-

<%= _('Plan Guidance Configuration') %>

-

<%= _('To help you write your plan, %{application_name} can show you guidance from a variety of organisations.') % {application_name: Rails.configuration.branding[:application][:name]} %>

-

<%= _('Select up to 6 organisations to see their guidance.') %>

- -
    - <%= render partial: "guidance_choices", - locals: {choices: @important_ggs, form: f, - current_selections: @selected_guidance_groups} %> -
- - <%= _('See guidance from additional organisations') %> - - -
-
+
+
+ <%= form_for plan, html: {method: :put, class: 'form-horizontal edit_plan' } do |f| %> +
+
+ <%= f.label(:title, _('Project Title'), class: 'control-label') %> +
+
+ <%= f.text_field( + :title, + class: 'form-control', + "aria-required": true) %> +
+
+ +
+
+
+ <%= f.hidden_field :visibility %> +
+ <%= f.label(:is_test, _('mock project for testing, practice, or educational purposes'), class: 'control-label') %> +
+
+ <%= check_box_tag(:is_test,1, @plan.is_test?) %> +
+
+
+
+ <%= f.label(:funder_name, _('Funder'), class: 'control-label') %> +
+
+ <%= f.text_field( + :funder_name, + class: 'form-control', + "aria-required": false) %> +
+
+
+
+ <%= f.label(:grant_number, _('Grant Number'), class: 'control-label') %> +
+
+ <%= f.text_field( + :grant_number, + class: 'form-control', + "aria-required": false) %> +
+
+ +
+
+
+
+ <%= f.label(:description, _('Project Abstract'), class: 'control-label') %> +
+
+ <%= f.text_area( + :description, + class: 'form-control tinymce', + "aria-required": false) %> +
+
+ +
+
+
+
+ <%= f.label(:identifier, _('ID'), class: 'control-label') %> +
+
+ <%= f.text_field( + :identifier, + class: 'form-control', + "aria-required": false) %> +
+
+ +
+
+
+

<%= _('Principal Investigator') %>

+
+
+ <%= f.label(:principal_investigator, _('Name'), class: 'control-label') %> +
+
+ <%= f.text_field( + :principal_investigator, + class: 'form-control', + "aria-required": false) %> +
+
+
+
+ <%= f.label(:principal_investigator_identifier, _('ORCID iD'), class: 'control-label') %> +
+
+ <%= f.text_field( + :principal_investigator_identifier, + class: 'form-control', + "aria-required": false) %> +
+
+
+
+ <%= f.label(:principal_investigator_email, _('Email'), class: 'control-label') %> +
+
+ <%= f.email_field( + :principal_investigator_email, + class: 'form-control', + "aria-required": false, + "data-validation": "email") %> +
+
+
+

<%= _('Data Contact Person') %>

+
+
+ <%= f.label(:data_contact, _('Name'), class: 'control-label') %> +
+
+ <%= f.text_field( + :data_contact, + class: 'form-control', + "aria-required": false) %> +
+
+
+
+ <%= f.label(:data_contact_phone, _('Phone'), class: 'control-label') %> +
+
+ <%= f.text_field( + :data_contact_phone, + class: 'form-control', + "aria-required": false) %> +
+
+
+
+ <%= f.label(:data_contact_email, _('Email'), class: 'control-label') %> +
+
+ <%= f.email_field( + :data_contact_email, + class: 'form-control', + "aria-required": false, + "data-validation": "email") %> +
+
+ <%= f.button(_('Submit'), class: "btn btn-default", type: "submit") %> + <% end %>
-<% end %> \ No newline at end of file +
+

<%= _('Plan Guidance Configuration') %>

+

<%= _('To help you write your plan, %{application_name} can show you guidance from a variety of organisations.') % + {application_name: Rails.configuration.branding[:application][:name]} %> +

+

<%= _('Select up to 6 organisations to see their guidance.') %>

+ +
+
\ No newline at end of file diff --git a/app/views/plans/_show_details.html.erb b/app/views/plans/_show_details.html.erb index 0919c84..8c9d78d 100644 --- a/app/views/plans/_show_details.html.erb +++ b/app/views/plans/_show_details.html.erb @@ -1,4 +1,4 @@ -
+
<%= _('Project Title') %>
<%= plan.title %>
<%= _('Funder') %>
@@ -7,26 +7,26 @@
<%= plan.grant_number %>
<%= _('Project Abstract') %>
<%= raw plan.description %>
-
<%= _('Id') %>
+
<%= _('ID') %>
<%= plan.identifier %>

-
-
<%= _('Principal Investigator') %>
-
-
<%= _('Name') %>
-
<%= plan.principal_investigator %>
-
<%= _('ORCID') %>
-
<%= plan.principal_investigator_identifier %>
-
<%= _('Email') %>
-
<%= plan.principal_investigator_email %>
+

<%= _('Principal Investigator') %>

+
+
<%= _('Name') %>
+
<%= plan.principal_investigator %>
+
<%= _('ORCID iD') %>
+
<%= plan.principal_investigator_identifier %>
+
<%= _('Email') %>
+
<%= plan.principal_investigator_email %>

-
-
<%= _('Data Contact Person') %>
-
+

<%= _('Data Contact Person') %>

+
<%= _('Name') %>
<%= plan.data_contact %>
+
<%= _('Phone') %>
+
<%= plan.data_contact_phone %>
<%= _('Email') %>
<%= plan.data_contact_email %>
diff --git a/app/views/plans/show.html.erb b/app/views/plans/show.html.erb index 85c18ec..8736900 100644 --- a/app/views/plans/show.html.erb +++ b/app/views/plans/show.html.erb @@ -7,39 +7,10 @@
-
- - - - -
-
- <% if @plan.editable_by?(current_user) %> - <%= render partial: 'edit_details', locals: {plan: @plan, visibility: @visibility} %> - <% else %> - <%= render partial: 'show_details', locals: {plan: @plan, visibility: @visibility} %> - <% end %> -
-
-
+ <% if @plan.editable_by?(current_user) %> + <%= render partial: 'edit_details', layout: 'shared/plan_navigation', locals: {plan: @plan, visibility: @visibility} %> + <% else %> + <%= render partial: 'show_details', locals: {plan: @plan, visibility: @visibility} %> + <% end %>
\ No newline at end of file diff --git a/app/views/shared/_plan_navigation.html.erb b/app/views/shared/_plan_navigation.html.erb index 717a0be..1c65b33 100644 --- a/app/views/shared/_plan_navigation.html.erb +++ b/app/views/shared/_plan_navigation.html.erb @@ -1,22 +1,21 @@ -
<%= yield %> diff --git a/lib/assets/.eslintrc.json b/lib/assets/.eslintrc.json index 95727a0..b2243c8 100644 --- a/lib/assets/.eslintrc.json +++ b/lib/assets/.eslintrc.json @@ -1,7 +1,7 @@ { "extends": "airbnb-base", "env": { - "jquery": true, - "jasmine": true + "jasmine": true, + "jquery": true } } diff --git a/lib/assets/javascripts/application.js b/lib/assets/javascripts/application.js index 6011eeb..262a25d 100644 --- a/lib/assets/javascripts/application.js +++ b/lib/assets/javascripts/application.js @@ -1,2 +1,3 @@ import './views/devise/devise'; import './views/contacts/new'; +import './views/plans/edit_details'; diff --git a/lib/assets/javascripts/utils/tinymce.js b/lib/assets/javascripts/utils/tinymce.js new file mode 100644 index 0000000..d0b7e37 --- /dev/null +++ b/lib/assets/javascripts/utils/tinymce.js @@ -0,0 +1,108 @@ +// Import TinyMCE +import tinymce from 'tinymce/tinymce'; +// Import TinyMCE theme +import 'tinymce/themes/modern/theme'; +// Plugins +import 'tinymce/plugins/table'; +import 'tinymce/plugins/autoresize'; +import 'tinymce/plugins/link'; +import 'tinymce/plugins/paste'; +import 'tinymce/plugins/advlist'; +// Other dependencies +import { isObject, isString } from './isType'; + +// Configuration extracted from https://www.tinymce.com/docs/advanced/usage-with-module-loaders/ +require.context( + 'file-loader?name=./javascripts/[path][name].[ext]&context=node_modules/tinymce!tinymce/skins', + true, + /.*/, +); + +export const defaultOptions = { + selector: '.tinymce', + statusbar: false, + menubar: false, + toolbar: 'bold italic | bullist numlist | link | table', + plugins: 'table autoresize link paste advlist', + advlist_bullet_styles: 'circle,disc,square', // Only disc bullets display on htmltoword + target_list: false, + autoresize_min_height: 130, + autoresize_bottom_margin: 10, + extended_valid_elements: 'iframe[tooltip] , a[href|target=_blank]', + paste_auto_cleanup_on_paste: true, + paste_remove_styles: true, + paste_retain_style_properties: 'none', + paste_convert_middot_lists: true, + paste_remove_styles_if_webkit: true, + paste_remove_spans: true, + paste_strip_class_attributes: 'all', + table_default_attributes: { + border: 1, + }, +}; + +export const Tinymce = { + /* + Initialises a tinymce editor given the object passed. If a non-valid object is passed, + the defaultOptions object is used instead + @param options - An object with tinyMCE properties + */ + init(options = {}) { + if (isObject(options)) { + tinymce.init($.extend(true, defaultOptions, options)); + } else { + tinymce.init(defaultOptions); + } + }, + /* + Finds any tinyMCE editor whose target element/textarea has the className passed + @param className - A string representing the class name of the tinyMCE editor + target element/textarea to look for + @return An Array of tinymce.Editor objects + */ + findEditorsByClassName(className) { + if (isString(className)) { + return tinymce.editors.reduce((acc, e) => { + if ($(e.getElement()).hasClass(className)) { + return acc.concat([e]); + } + return acc; + }, []); + } + return []; + }, + /* + Finds a tinyMCE editor whose target element/textarea has the id passed + @param id - A string representing the id of the tinyMCE editor target + element/textarea to look for + @return tinymce.Editor object, otherwise undefined + */ + findEditorById(id) { + if (isString(id)) { + return tinymce.editors.find(el => el.id === id); + } + return undefined; + }, + /* + Destroy every editor instance whose target element/textarea has the className passed. This + method executes for each editor the method defined at tinymce.Editor.destroy (e.g. https://www.tinymce.com/docs/api/tinymce/tinymce.editor/#destroy). + @param className - A string representing the class name of the tinyMCE editor + target element/textarea to look for + @return undefined + */ + destroyEditorsByClassName(className) { + const editors = this.findEditorsByClassName(className); + editors.forEach(ed => ed.destroy(false)); + }, + /* + Destroy an editor instance whose target element/textarea has HTML id passed. This method + executes tinymce.Editor.destroy (e.g. https://www.tinymce.com/docs/api/tinymce/tinymce.editor/#destroy) for a successfull id found. + @return undefined + */ + destroyEditorById(id) { + const editor = this.findEditorById(id); + if (editor) { + editor.destroy(false); + } + }, +}; diff --git a/lib/assets/javascripts/utils/tinymceSpec.js b/lib/assets/javascripts/utils/tinymceSpec.js new file mode 100644 index 0000000..2c6439f --- /dev/null +++ b/lib/assets/javascripts/utils/tinymceSpec.js @@ -0,0 +1,50 @@ +import { Tinymce } from './tinymce'; + +beforeEach(() => { + $('body').append(''); + $('body').append(''); + Tinymce.init({ selector: '.test' }); +}); + +describe('findEditorsByClassName test suite', () => { + it('expect two editors with class name test', () => expect(Tinymce.findEditorsByClassName('test').length).toBe(2)); + it('expect zero editors with class name whatever', () => expect(Tinymce.findEditorsByClassName('whatever').length).toBe(0)); +}); + +describe('findEditorById test suite', () => { + it('expect editor with id test1 to be defined', () => expect(Tinymce.findEditorById('test1')).toBeDefined()); + it('expect editor with id test2 to be defined', () => expect(Tinymce.findEditorById('test2')).toBeDefined()); + it('expect editor with id whatever to be undefined', () => expect(Tinymce.findEditorById('whatever')).not.toBeDefined()); +}); + +describe('destroyEditorsByClassName test suite', () => { + it('expect remaining two editors', () => { + Tinymce.destroyEditorsByClassName('whatever'); + expect(Tinymce.findEditorsByClassName('test').length).toBe(2); + }); + it('expect remaining zero editors', () => { + Tinymce.destroyEditorsByClassName('test'); + expect(Tinymce.findEditorsByClassName('test').length).toBe(0); + }); +}); + +describe('destroyEditorsById test suite', () => { + it('expect remaining two editors', () => { + Tinymce.destroyEditorById('test3'); + expect(Tinymce.findEditorsByClassName('test').length).toBe(2); + }); + it('expect remaining one editor', () => { + Tinymce.destroyEditorById('test1'); + expect(Tinymce.findEditorsByClassName('test').length).toBe(1); + }); + it('expect remaining zero editors', () => { + Tinymce.destroyEditorById('test1'); + Tinymce.destroyEditorById('test2'); + expect(Tinymce.findEditorsByClassName('test').length).toBe(0); + }); +}); + +afterEach(() => { + $('body').html(''); +}); + diff --git a/lib/assets/javascripts/views/plans/edit_details.js b/lib/assets/javascripts/views/plans/edit_details.js new file mode 100644 index 0000000..85514c0 --- /dev/null +++ b/lib/assets/javascripts/views/plans/edit_details.js @@ -0,0 +1,14 @@ +import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; +import 'bootstrap-sass/assets/javascripts/bootstrap/popover'; +import { Tinymce } from '../../utils/tinymce'; +import ariatiseForm from '../../utils/ariatiseForm'; + +$(() => { + $('[data-toggle="tooltip"]').tooltip(); + $('[data-toggle="popover"]').popover(); + Tinymce.init(); + $('#is_test').click(() => { + $('#plan_visibility').val($(this).is(':checked') ? 'is_test' : 'privately_visible'); + }); + ariatiseForm({ selector: '.edit_plan' }); +}); diff --git a/lib/assets/karma.conf.js b/lib/assets/karma.conf.js index 3e95689..6e5f5d4 100644 --- a/lib/assets/karma.conf.js +++ b/lib/assets/karma.conf.js @@ -10,8 +10,7 @@ // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], - + frameworks: ['jasmine', 'jquery-3.2.1'], // list of files / patterns to load in the browser files: [ diff --git a/lib/assets/package.json b/lib/assets/package.json index 4d13c5e..9324098 100644 --- a/lib/assets/package.json +++ b/lib/assets/package.json @@ -46,6 +46,7 @@ "karma": "^1.7.0", "karma-chrome-launcher": "^2.2.0", "karma-jasmine": "^1.1.0", + "karma-jquery": "^0.2.2", "karma-webpack": "^2.0.4", "node-sass": "^4.5.3", "sass-loader": "^6.0.6", diff --git a/lib/assets/webpack.config.js b/lib/assets/webpack.config.js index 0011906..71f1ec7 100644 --- a/lib/assets/webpack.config.js +++ b/lib/assets/webpack.config.js @@ -15,7 +15,7 @@ context: __dirname, entry: { - vendor: ['jquery', 'tinymce/tinymce', 'tinymce/themes/modern/theme'], + vendor: ['jquery'], application: ['./javascripts/application.js', './stylesheets/application.scss'], }, @@ -47,7 +47,7 @@ }), }, { - test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/, + test: /(?:fonts\/bootstrap\/.*)|(?:font-awesome\/fonts\/.*)(?:\.woff2?$|\.ttf$|\.eot$|\.svg$)/, use: [ { loader: 'file-loader', @@ -64,14 +64,14 @@ plugins: [ extractSass, + new webpack.ProvidePlugin({ // Load jquery module automatically instead of import everywhere + jQuery: 'jquery', + $: 'jquery', + }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: Infinity, }), - new webpack.ProvidePlugin({ // Load jquery module automatically instead of import everywhere - $: 'jquery', - jQuery: 'jquery', - }), new CopyWebPackPlugin([ // Copies every file under images or videos { from: './images/**/*', to: `${destPath}/` }, { from: './videos/**/*', to: `${destPath}/` },