diff --git a/README.md b/README.md index 82e735b..06fa9cf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Add new dropdown elements on detailed issue page to dynamically update issue's s ### Installation -* Clone repo into plugins directory : `git clone https://github.com/Ilogeek/redmine_issue_dynamic_edit.git` +* Clone repo into plugins directory : `git clone https://github.com/Ilogeek/redmine_issue_dynamic_edit.git` (be sure that the parent folder is called `redmine_issue_dynamic_edit`) * Restart your Redmine instance ### Customization @@ -18,6 +18,7 @@ This plugin uses [FontAwesome icons](http://fontawesome.io/) ### Changelog +* **v 0.4.0** : fixed Github issues #2, #4, #9. Edited dropdown display * **v 0.3.0** : start date, due date, ratio and estimated time fields are now dynamically editable. Translation files added (en, fr). Log added in console when AJAX fails * **v 0.2.0** : fixed "conflict" when trying to add a note after an update from dropdowns. New method used, REST API is not required anymore * **v 0.1.0** : initial commit \ No newline at end of file diff --git a/README.rdoc b/README.rdoc index e83982f..2b8aaaa 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,3 +1,3 @@ = redmine_issue_dynamic_edit -Add new dropdowns elements on detailed issue page to dynamically update issue's status, assignee and priority directly in the details block of the issue +Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue. diff --git a/assets/javascripts/issue_dynamic_edit.js b/assets/javascripts/issue_dynamic_edit.js index 2325da6..a7a4e34 100644 --- a/assets/javascripts/issue_dynamic_edit.js +++ b/assets/javascripts/issue_dynamic_edit.js @@ -12,65 +12,90 @@ var cssId = 'fontAwesome'; head.appendChild(link); } -/* Update height of progress bar because of dropdown */ -var dynamicEditSelectHeight = $('.dynamicEditSelect').height(); -var progressBarHeight = $('table.progress').height(); -$('table.progress').css({'marginTop' : (dynamicEditSelectHeight - progressBarHeight)/2 + "px" }); +$(document).on('click', function(e){ + $('.issue .attributes .attribute .value').removeClass('edited'); + if($(e.target).closest('.value').length) { + $(e.target).closest('.value').addClass('edited'); + } +}); /* Put new dropdown lists in the detailed info block */ if($('#statusListDropdown').length > 0) { var htmlCopy = $('#statusListDropdown').get(0).outerHTML; $('#statusListDropdown').remove(); - $('.details .attributes .status.attribute .value').html(htmlCopy); + $('.details .attributes .status.attribute .value').html( '' + + $('.details .attributes .status.attribute .value').html() + ' ' + + htmlCopy); } if($('#usersListDropdown').length > 0) { var htmlCopy = $('#usersListDropdown').get(0).outerHTML; $('#usersListDropdown').remove(); - $('.details .attributes .assigned-to.attribute .value').html(htmlCopy); + $('.details .attributes .assigned-to.attribute .value').html( '' + + $('.details .attributes .assigned-to.attribute .value').html() + ' ' + + htmlCopy); } if($('#prioritiesListDropdown').length > 0) { var htmlCopy = $('#prioritiesListDropdown').get(0).outerHTML; $('#prioritiesListDropdown').remove(); - $('.details .attributes .priority.attribute .value').html(htmlCopy); + $('.details .attributes .priority.attribute .value').html( '' + + $('.details .attributes .priority.attribute .value').html() + ' ' + + htmlCopy); } if($('#doneRatioListDropdown').length > 0) { var htmlCopy = $('#doneRatioListDropdown').get(0).outerHTML; $('#doneRatioListDropdown').remove(); - $('.details .attributes .progress.attribute .percent').html(htmlCopy); + $('.details .attributes .progress.attribute .value').html('' + + $('.details .attributes .progress.attribute .value').html() + ' ' + + htmlCopy); } if($('#EstimatedTimeInput').length > 0) { var htmlCopy = $('#EstimatedTimeInput').get(0).outerHTML; $('#EstimatedTimeInput').remove(); - $('.details .attributes .estimated-hours.attribute .value').html(htmlCopy); + $('.details .attributes .estimated-hours.attribute .value').html('' + + $('.details .attributes .estimated-hours.attribute .value').html() + ' ' + + htmlCopy); } if($('#StartDateInput').length > 0) { var htmlCopy = $('#StartDateInput').get(0).outerHTML; $('#StartDateInput').remove(); - $('.details .attributes .start-date.attribute .value').html(htmlCopy); + $('.details .attributes .start-date.attribute .value').html('' + + $('.details .attributes .start-date.attribute .value').html() + ' ' + + htmlCopy); } if($('#DueDateInput').length > 0) { var htmlCopy = $('#DueDateInput').get(0).outerHTML; $('#DueDateInput').remove(); - $('.details .attributes .due-date.attribute .value').html(htmlCopy); + $('.details .attributes .due-date.attribute .value').html('' + + $('.details .attributes .due-date.attribute .value').html() + ' ' + + htmlCopy); } +$('body').on('click', '.btn.close', function(e){ + e.preventDefault(); + $(e.target).closest('.value').removeClass('edited'); + return false; +}); + function issueDynamicUpdate(field_name, field_value, type, cssClass){ + /* hide edit field */ + $('.details .attributes .' + cssClass + '.attribute .value').removeClass('edited'); + /* add spin notification */ if(type == "progress") { // specific case for progress bar - $('.details .attributes .' + cssClass + '.attribute .percent').append(' '); + $('.details .attributes .' + cssClass + '.attribute .value').append(' '); } else { $('.details .attributes .' + cssClass + '.attribute .value').append(' '); } - /* update value displayed to "move" edit pen icon */ - $('.details .attributes .' + cssClass + '.attribute .selectedValue span').html(function(){ + /* update value displayed */ + $('.details .attributes .' + cssClass + '.attribute .showValue').html(function(){ if(type == "select") { return $('.details .attributes .' + cssClass + '.attribute .value select option:selected').html() @@ -111,10 +136,11 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ if(type == "progress") { // specific case for progress bar, we need to update the progress bar view var progressBar = ""; var percentTodo = 100 - parseInt(field_value); - progressBar += ""; - progressBar += ""; + if(field_value != 0) { progressBar += ""; } + if(percentTodo != 0) { progressBar += ""; } progressBar += ""; $('.details .attributes .' + cssClass + '.attribute table.progress').attr('class', 'progress progress-' + field_value).html(progressBar); + $('.details .attributes .' + cssClass + ' .percent').html(field_value + "%"); } else if( type == "date") { // specific case for start date and due date, we have to update min and max date allowed if(field_name == "start_date") { @@ -159,11 +185,21 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ var domSelectStatus = $('body').find('#statusListDropdown select'); domSelectStatus.on('change', function(e){ issueDynamicUpdate('status_id', domSelectStatus.val(), 'select', 'status'); + + /* update the classes status from */ + $("#content > div.issue").removeClass(function (index, className) { + return (className.match (/(^|\s)status-\S+/g) || []).join(' '); + }).addClass('status-' + domSelectStatus.val()); }); /* end on change domSelectStatus */ var domSelectPriorities = $('body').find('#prioritiesListDropdown select'); domSelectPriorities.on('change', function(e){ issueDynamicUpdate('priority_id', domSelectPriorities.val(), 'select', 'priority'); + + /* update the classes priority from */ + $("#content > div.issue").removeClass(function (index, className) { + return (className.match (/(^|\s)priority-\S+/g) || []).join(' '); + }).addClass('priority-' + domSelectStatus.val()); }); /* end on change domSelectPriorities */ var domSelectUsers = $('body').find('#usersListDropdown select'); @@ -174,10 +210,10 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ var domSelectRatio = $('body').find('#doneRatioListDropdown select'); domSelectRatio.on('change', function(e){ issueDynamicUpdate('done_ratio', domSelectRatio.val(), 'progress', 'progress'); - }); /* end on change domSelectUsers */ + }); /* end on change domSelectRatio */ var domInputEstimatedTime = $('body').find('#EstimatedTimeInput input'); - $('#EstimatedTimeInput a.btn').on('click', function(e) + $('#EstimatedTimeInput a.btn.validate').on('click', function(e) { e.preventDefault(); $('.estimated-hours .value .error').remove(); @@ -197,12 +233,12 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ $('.details .attributes .estimated-hours.attribute .value input').val() ); if (e.keyCode == 13) { - $('#EstimatedTimeInput a.btn').click(); + $('#EstimatedTimeInput a.btn.validate').click(); } });/* end EstimatedTime */ var domInputStartDate = $('body').find('#StartDateInput input'); - $('#StartDateInput a.btn').on('click', function(e) + $('#StartDateInput a.btn.validate').on('click', function(e) { e.preventDefault(); $('.start-date .value .error').remove(); @@ -218,12 +254,12 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ domInputStartDate.on('keyup', function(e){ if (e.keyCode == 13) { - $('#StartDateInput a.btn').click(); + $('#StartDateInput a.btn.validate').click(); } });/* end StartDate */ var domInputDueDate = $('body').find('#DueDateInput input'); - $('#DueDateInput a.btn').on('click', function(e) + $('#DueDateInput a.btn.validate').on('click', function(e) { e.preventDefault(); $('.due-date .value .error').remove(); @@ -239,6 +275,6 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ domInputDueDate.on('keyup', function(e){ if (e.keyCode == 13) { - $('#DueDateInput a.btn').click(); + $('#DueDateInput a.btn.validate').click(); } });/* end StartDate */ \ No newline at end of file diff --git a/assets/stylesheets/issue_dynamic_edit.css b/assets/stylesheets/issue_dynamic_edit.css index 79ccce8..5f80409 100644 --- a/assets/stylesheets/issue_dynamic_edit.css +++ b/assets/stylesheets/issue_dynamic_edit.css @@ -1,5 +1,10 @@ -div.issue .attribute .value { +div.issue .attribute .value, div.issue .splitcontent { overflow: visible; + position: relative; +} + +div.issue .attribute .value .percent { + display: inline-block; } /* Progress bar fix */ @@ -7,116 +12,76 @@ table.progress { margin-right: 10px; } -/* If you want to hide edit (pencil) icon, set opacity to 0 below : */ -.selectedValue { - position: absolute; - white-space: nowrap; - top: 2px; - right: 0; - left: 5px; - pointer-events: none; - opacity: 1; /* <---------------- */ -} - -.attribute:hover .selectedValue { - opacity: 0; -} - -.selectedValue .transparent { - opacity: 0; -} - .attribute .error { color: #e74c3c; margin-left: 3px; } -/* Input Style */ -.dynamicEditInput { - position: relative; - border: 1px solid transparent; - border-radius: 3px; - padding: 4px; - margin-left: -5px; - padding-right: 0; +.value .fa-pencil { + opacity: 0; +} + +.issue.details .showValue { + cursor: pointer; +} + +.issue.details:hover .fa-pencil { + opacity: 1; +} + +.dynamicEdit { display: inline-block; + position:absolute; + opacity: 0; + left:0; + bottom: 100%; + margin-bottom: 5px; + box-sizing: border-box; + padding: 10px; + border-radius: 3px; + background: white; + pointer-events: none; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + width: max-content; } -.dynamicEditInput input { - border: 0px; - font: inherit; - color: inherit; - background: transparent; - padding: 0; +.edited .dynamicEdit { + pointer-events: auto; } -.dynamicEditInput .selectedValue { - top:0; left:0; -} - -.attribute:hover .dynamicEditInput { - border-color: #e0e2e3; +div.issue .attribute .value.edited .dynamicEdit { + opacity: 1; } .attribute .btn-primary { - border-left: 1px solid #e0e2e3; padding: 4px; - opacity: 0; - pointer-events: none; -} - -.attribute:hover .btn-primary { - pointer-events: auto; - opacity: 1; -} - -/* End Input Style */ - -/* Select Style */ -.dynamicEditSelect select { - border-color: transparent; - border: 0; - border-bottom: 1px solid transparent; - border-radius: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: none; - font-size: inherit; - color: inherit; - font-family: inherit; - background: transparent; - padding-left:0; -} - -.dynamicEditSelect { - padding-left: 5px; - margin-left: -6px; /* padding + border */ - display: inline-block; - border: 1px solid transparent; + color: white; border-radius: 3px; - position:relative; -} - -.attribute:hover .dynamicEditSelect { - border-color: #e0e2e3; -} - -.dynamicEditSelect i.dropdown { - position:absolute; - right: 5px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - opacity: 0; + padding: 3px; } -.attribute:hover .dynamicEditSelect i.dropdown { - opacity: 1; +.attribute .btn-primary.close { + background: #c0392b; } -.dynamicEditSelect select option[disabled="disabled"] +.attribute .btn-primary.validate { + background: #27ae60; +} + +.dynamicEdit select { + border:none; + background-image:none; + background-color:transparent; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + font-size: inherit; + font-family: inherit; + border: 1px solid #ccc; + border-radius: 3px; +} + +.dynamicEdit select option[disabled="disabled"] { display:none; -} -/* End Select Style */ \ No newline at end of file +} \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 61b16c4..4ddfb1b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,4 +3,5 @@ en: ide_txt_error_positive_number : 'It must be a positive number' ide_txt_error_start_date : 'Start date must be anterior to due date' ide_txt_error_due_date : 'Due date must be posterior to start date' - ide_txt_error_ajax_call : 'Error (check your JS console)' \ No newline at end of file + ide_txt_error_ajax_call : 'Error (check your JS console)' + ide_txt_cancel_btn : 'Cancel the modification' \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8926aa0..8081b32 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -3,4 +3,5 @@ fr: ide_txt_error_positive_number : 'Le nombre doit être positif' ide_txt_error_start_date : "La date de début doit être antérieure à la date d'échéance" ide_txt_error_due_date : "La date d'échéance doit être postérieure à la date de début" - ide_txt_error_ajax_call : 'Erreur (logs dans la console JS)' \ No newline at end of file + ide_txt_error_ajax_call : 'Erreur (logs dans la console JS)' + ide_txt_cancel_btn : 'Annuler la modification' \ No newline at end of file diff --git a/init.rb b/init.rb index 89641e7..60e36f3 100644 --- a/init.rb +++ b/init.rb @@ -6,7 +6,7 @@ Redmine::Plugin.register :redmine_issue_dynamic_edit do name 'Redmine Dynamic edit Issue plugin' author 'Hugo Zilliox' description 'Allows users to dynamically update issue attributes in detailed view' - version '0.3.1' + version '0.4.0' url 'https://github.com/ilogeek/redmine_issue_dynamic_edit' author_url 'https://hzilliox.fr' end diff --git a/lib/details_issue_hooks.rb b/lib/details_issue_hooks.rb index a64548d..2744fa2 100644 --- a/lib/details_issue_hooks.rb +++ b/lib/details_issue_hooks.rb @@ -17,19 +17,21 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener request = context[:request] issue_id = request.path_parameters[:id] back = request.env['HTTP_REFERER'] + + o = '' if (issue_id) issue = Issue.find(issue_id) if (issue) if (User.current.allowed_to?(:edit_issues, project)) - o = '' - # o << issue.to_json + + # if there's a JS error, we hide the generated values + o << '
' # Status dropdown statuses = issue.new_statuses_allowed_to(User.current) if (!statuses.empty?) - o << "" - o << "
#{issue.status}
" + o << "" o << "" + o << "
" end # Users dropdown # userCanChangeAssignee = User.current.allowed_to?(:edit_assigned_to, @project, :global => true) assignables = project.assignable_users + o << assignables.to_json if (!assignables.empty?) - o << "" - o << "
#{issue.assigned_to}
" + o << "" o << "" + o << "
" end # Priorities dropdown priorities = IssuePriority.all if(!priorities.empty?) - o << "" - o << "
#{issue.priority}
" + o << "" o << "" + o << "
" end # %done dropdown percent = 0 - o << "" - o << "
#{issue.done_ratio}%
" + o << "" o << "" + o << "
" # Estimated_time dropdown - o << "" - o << "
#{issue.estimated_hours}
" + o << "" o << " " - o << "" + o << "" + o << " " o << "" # Start date - o << "" - o << "
XXXX/XX/XX
" + o << "" o << " " - o << "" + o << " " + o << " " o << "" o << "" # Due date - o << "" - o << "
XXXX/XX/XX
" + o << "" o << " " - o << "" + o << " " + o << " " o << "" o << "" + + # closing the display none div parent + o << "
" return o end end