From ad37b96b531c077ee12c0eb55d69666349243f41 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 29 Dec 2021 18:34:55 +0100 Subject: [PATCH] 0.8.0 update - Complete rework. Compatible with last Redmine version. New settings added (#70 #88). Removed external lib (FontAwesome) (#74). Mobile style added (#87). Print style added (#84). Bug fix (#79, #85) --- README.md | 6 +- assets/javascripts/issue_dynamic_edit.js | 949 ++++++------------ .../issue_dynamic_edit_configuration_file.js | 18 +- assets/stylesheets/issue_dynamic_edit.css | 167 +-- config/locales/de.yml | 9 - config/locales/en.yml | 11 +- config/locales/fr.yml | 11 +- init.rb | 2 +- lib/details_issue_hooks.rb | 240 +---- 9 files changed, 439 insertions(+), 974 deletions(-) delete mode 100644 config/locales/de.yml diff --git a/README.md b/README.md index 58e1709..2a83063 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,16 @@ You can set some settings by editing the file `assets/javascripts/issue_dynamic_ * **\_CONF\_LISTENER\_TYPE\_ICON** : Choose which action will trigger the apparition of the edition block when fired from the pencil icon (by default: same as **\_CONF\_LISTENER\_TYPE\_VALUE**). Allowed value : `none`, `click`, `dblclick` * **\_CONF\_LISTENER\_TARGET** : Choose which area will trigger the apparition of the edition block * **\_CONF\_EXCLUDED\_FIELD\_ID** : Choose which fields to exclude. They won't have the edit block and pencil. Eg: `TitleInput`, `DescriptionInput`, `statusListDropdown` ... +* **\_CONF\_CHECK\_ISSUE\_UPDATE\_CONFLICT** : Choose if you allow current user to override all modifications performed by other users while editing the issue ### 🎨 Customization Feel free to edit `assets/stylesheets/issue_dynamic_edit.css` to update the look of your fields depending on your current Redmine Theme. -This plugin uses [FontAwesome icons 4.7](https://fontawesome.com/v4.7.0/) - ### 🆕 Changelog -* **v 0.7.2** : New settings added into config file (`\_CONF\_DISPLAY\_EDIT\_ICON` and `\_CONF\_LISTENER\_TYPE\_ICON`) see Configuration part for more info ; new event `none` for `\_CONF\_LISTENER\_TYPE\_VALUE` disabling listener on value ; css fix +* **v 0.8.0** : Complete rework. Compatible with last Redmine version. New settings added : `_CONF_CHECK_ISSUE_UPDATE_CONFLICT` (#70 #88). Removed external lib (FontAwesome) (#74). Mobile style added (#87). Print style added (#84). Bug fix (#79, #85) +* **v 0.7.2** : New settings added into config file (`_CONF_DISPLAY_EDIT_ICON` and `_CONF_LISTENER_TYPE_ICON`) see Configuration part for more info ; new event `none` for `_CONF_LISTENER_TYPE_VALUE` disabling listener on value ; css fix * **v 0.7.1** : Fixed incorrect DOM structure if user has read only access to the issue (#61 #64) * **v 0.7.0** : Category filter by project added (#55) and prevent dialog closing when using fa-pencil selector (#59) * **v 0.6.9** : Category field support (Github request #54) diff --git a/assets/javascripts/issue_dynamic_edit.js b/assets/javascripts/issue_dynamic_edit.js index 428e302..bce218c 100644 --- a/assets/javascripts/issue_dynamic_edit.js +++ b/assets/javascripts/issue_dynamic_edit.js @@ -4,9 +4,21 @@ var _CONF_FORCE_HTTPS = _CONF_FORCE_HTTPS || false; var _CONF_DISPLAY_EDIT_ICON = _CONF_DISPLAY_EDIT_ICON || "single"; var _CONF_LISTENER_TYPE_VALUE = _CONF_LISTENER_TYPE_VALUE || "click"; -var _CONF_LISTENER_TYPE_ICON = _CONF_LISTENER_TYPE_ICON || "click"; +var _CONF_LISTENER_TYPE_ICON = _CONF_LISTENER_TYPE_ICON || "none"; var _CONF_LISTENER_TARGET = _CONF_LISTENER_TARGET || "value"; var _CONF_EXCLUDED_FIELD_ID = _CONF_EXCLUDED_FIELD_ID || []; +var _CONF_CHECK_ISSUE_UPDATE_CONFLICT = _CONF_CHECK_ISSUE_UPDATE_CONFLICT || false; + +_CONF_LISTENER_TARGET = _CONF_LISTENER_TARGET === "all" ? "*" : _CONF_LISTENER_TARGET; + +/* + * SVG ICONS + * Source : https://www.iconfinder.com/iconsets/glyphs + */ + +var SVG_EDIT = ''; +var SVG_VALID = ''; +var SVG_CANCEL = ''; /* * Allow inclusion from other page @@ -22,685 +34,324 @@ if (_CONF_FORCE_HTTPS) { * or if user has to hover every element to discover if (s)he can edit it */ if (_CONF_DISPLAY_EDIT_ICON === "block"){ - $('body.controller-issues.action-show .issue.details').addClass('showDynamicEdit'); + $('body.controller-issues.action-show .issue.details').addClass('showPencils'); } +/* Generate edit block */ +var getEditFormHTML = function(attribute){ + var formElement = $('#issue_' + attribute + "_id"); + formElement = formElement.length ? formElement : $('#issue_' + attribute); + formElement = formElement.length ? formElement : $('#' + attribute); - -/* FontAwesome inclusion */ -var cssId = 'fontAwesome'; - -if (!document.getElementById(cssId)) { - var head = document.getElementsByTagName('head')[0]; - var link = document.createElement('link'); - link.id = cssId; - link.rel = 'stylesheet'; - link.type = 'text/css'; - link.href = 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'; - link.media = 'all'; - head.appendChild(link); -} - -function editActionHandler(e) { - $('.issue .attributes .attribute .value').removeClass('edited'); - // bind click to show edit block if click inside an edit box or on trigger, except button inside edit box - if(!$(e.target).closest('a.btn.btn-primary').length && - ($(e.target).closest('.' + _CONF_LISTENER_TARGET).length || - $(e.target).closest('span.dynamicEdit').length) - ) { - $(e.target).closest('.value').addClass('edited'); - } - if ($(e.target).closest('a').length) { return; } - if ($(e.target).closest('.' + _CONF_LISTENER_TARGET).length) { - // avoid text selection if dblclick - var sel = window.getSelection ? window.getSelection() : document.selection; - var activeElement = document.activeElement; - var inputs = ['input', 'select', 'button', 'textarea']; - - if (sel && inputs.indexOf(activeElement.tagName.toLowerCase()) === -1) { - if (sel.removeAllRanges) { - sel.removeAllRanges(); - } else if (sel.empty) { - sel.empty(); - } - } - } -} - -// Listen on events on a whole line for any field -if(_CONF_LISTENER_TYPE_VALUE !== "none") { - $(document).on(_CONF_LISTENER_TYPE_VALUE, editActionHandler); -} else { - $('body.controller-issues.action-show .issue.details').addClass('no-cursor'); -} - -// If a supplementary type of event is set specifically for the dynamic edit icon, -// add another listener for it -if (_CONF_LISTENER_TYPE_VALUE !== _CONF_LISTENER_TYPE_ICON && _CONF_LISTENER_TYPE_ICON !== "none") { - $(document).on(_CONF_LISTENER_TYPE_ICON, '.fa-pencil.dynamicEditIcon' , function (e) { - editActionHandler(e); - }); -} - -function isExcluded(elmt_id) { - return _CONF_EXCLUDED_FIELD_ID.indexOf(elmt_id) > -1; -} - -function initEditFields() { - /* Put new dropdown lists in the detailed info block */ - if ($('#statusListDropdown').length > 0 && !isExcluded('statusListDropdown')) { - var htmlCopy = $('#statusListDropdown').get(0).outerHTML; - $('#statusListDropdown').remove(); - $('.details .attributes .status.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .status.attribute .value').html() + - ' ' - ); - } - - if ($('#prioritiesListDropdown').length > 0 && !isExcluded('prioritiesListDropdown')) { - var htmlCopy = $('#prioritiesListDropdown').get(0).outerHTML; - $('#prioritiesListDropdown').remove(); - $('.details .attributes .priority.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .priority.attribute .value').html() + - ' ' - ); - } - - if ($('#categoriesListDropdown').length > 0 && !isExcluded('categoriesListDropdown')) { - var htmlCopy = $('#categoriesListDropdown').get(0).outerHTML; - $('#categoriesListDropdown').remove(); - $('.details .attributes .category.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .category.attribute .value').html() + - ' ' - ); - } - - if ($('#doneRatioListDropdown').length > 0 && !isExcluded('doneRatioListDropdown')) { - var htmlCopy = $('#doneRatioListDropdown').get(0).outerHTML; - $('#doneRatioListDropdown').remove(); - $('.details .attributes .progress.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .progress.attribute .value').html() + ' ' - ); - } - - if ($('#EstimatedTimeInput').length > 0 && !isExcluded('EstimatedTimeInput')) { - var htmlCopy = $('#EstimatedTimeInput').get(0).outerHTML; - $('#EstimatedTimeInput').remove(); - $('.details .attributes .estimated-hours.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .estimated-hours.attribute .value').html() + - ' ' - ); - } - - if ($('#StartDateInput').length > 0 && !isExcluded('StartDateInput')) { - var htmlCopy = $('#StartDateInput').get(0).outerHTML; - $('#StartDateInput').remove(); - $('.details .attributes .start-date.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .start-date.attribute .value').html() + - ' ' - ); - } - - if ($('#DueDateInput').length > 0 && !isExcluded('DueDateInput')) { - var htmlCopy = $('#DueDateInput').get(0).outerHTML; - $('#DueDateInput').remove(); - $('.details .attributes .due-date.attribute .value').html( - htmlCopy + - '' + - $('.details .attributes .due-date.attribute .value').html() + - ' ' - ); - } - - if ($('#TitleInput').length > 0 && !isExcluded('TitleInput')) { - var htmlCopy = $('#TitleInput').get(0).outerHTML; - $('#TitleInput').remove(); - $('.subject h3').html( - htmlCopy + - '' + - $('.subject h3').html() + - ' ' - ).addClass('value'); - } - - if ($('#DescriptionInput').length > 0 && !isExcluded('DescriptionInput')) { - var htmlCopy = $('#DescriptionInput').get(0).outerHTML; - $('#DescriptionInput').remove(); - $('div.description .wiki').html( - htmlCopy + - ' ' + - $('div.description .wiki').html() + '' - ).addClass('value'); - } - - if ($('select#issue_assigned_to_id').length > 0 && !isExcluded('issue_assigned_to_id')) { - var htmlCopy = $('select#issue_assigned_to_id').get(0).outerHTML; - // 2 technics with simple or double quote (safety first) - htmlCopy = htmlCopy.replace(/id="/g, 'id="dynamic_').replace(/id='/g, "id='dynamic_"); - - var editHTML = ""; - editHTML += htmlCopy; - editHTML += " "; - editHTML += ""; - - $('.details .attributes .assigned-to.attribute .value').html( - editHTML + - '' + - $('.details .attributes .assigned-to.attribute .value').html() + - ' ' - ); - } - - if ($('select#issue_fixed_version_id').length > 0 && !isExcluded('issue_fixed_version_id')) { - var htmlCopy = $('select#issue_fixed_version_id').get(0).outerHTML; - // 2 technics with simple or double quote (safety first) - htmlCopy = htmlCopy.replace(/id="/g, 'id="dynamic_').replace(/id='/g, "id='dynamic_"); - - var editHTML = ""; - editHTML += htmlCopy; - editHTML += " "; - editHTML += ""; - - $('.details .attributes .fixed-version.attribute .value').html( - editHTML + - '' + - $('.details .attributes .fixed-version.attribute .value').html() + - ' ' - ); - } - - for (var i = 0 ; i < CF_VALUE_JSON.length ; i++) { - var info = CF_VALUE_JSON[i].custom_field; - var value = CF_VALUE_JSON[i].value; - - if (info.visible && info.editable && !isExcluded("issue_custom_field_values_" + info.id)) { - if ( - $('.details .attributes .cf_' + info.id + '.attribute .value').length && - ( - $('#issue_custom_field_values_' + info.id).length || - $('input[name=issue\\[custom_field_values\\]\\[' + info.id + '\\]\\[\\]]').length - ) - ) { - // if single input first case, else checkboxes second case - if($('#issue_custom_field_values_' + info.id).length) { - var htmlCopy = $('#issue_custom_field_values_' + info.id).get(0).outerHTML; - } else { - var htmlCopy = $('input[name=issue\\[custom_field_values\\]\\[' + info.id + '\\]\\[\\]]').parents('.check_box_group').get(0).outerHTML; - } - - // 2 technics with simple or double quote (safety first) - htmlCopy = htmlCopy.replace(/id="/g, 'id="dynamic_').replace(/id='/g, "id='dynamic_"); - htmlCopy = htmlCopy.replace(/class="/g, 'class="cf_' + info.id + ' ').replace(/class='/g, "class='cf_" + info.id + " "); - - var editHTML = ""; - - editHTML += htmlCopy; - editHTML += " "; - editHTML += " "; - editHTML += ""; - - $('.details .attributes .cf_' + info.id + '.attribute .value').html( - editHTML + - '' + - $('.details .attributes .cf_' + info.id + '.attribute .value').html() + - ' ' - ); - - if (info.field_format == "date") { - if ( - $('body').find('#dynamic_issue_custom_field_values_' + info.id).length && - $('body').find('#dynamic_issue_custom_field_values_' + info.id).datepickerFallback instanceof Function && - typeof datepickerOptions !== 'undefined' - ) { - $('body').find('#dynamic_issue_custom_field_values_' + info.id).datepickerFallback(datepickerOptions); - } - } - - cf_datetime = $('body').find('#dynamic_issue_custom_field_values_' + info.id); - if (info.field_format == "datetime") { - if ( - cf_datetime.length && - typeof datetimepickerOptions !== 'undefined' - ) { - cf_datetime.datetimepicker(datetimepickerOptions); - } - } - } - } - } -} - -initEditFields(); - -/* Add required style to attributes */ -function updateRequiredFields(reqFieldsArray) { - for (var i = 0; i < reqFieldsArray.length; i++) { - var htmlLabel = reqFieldsArray[i].replace(/_/g, '-'); - $('.issue.details .attribute.' + htmlLabel + ' .label').html( - '' + - $('.issue.details .attribute.' + htmlLabel + ' .label').html() + - ' *' - ); - } -} - -if ($('#required_field_array').length) { - updateRequiredFields(JSON.parse($('#required_field_array').html())); -} - -$('body.controller-issues.action-show').on('click', '.btn.close', function(e) { - e.preventDefault(); - $(e.target).closest('.value').removeClass('edited'); - return false; -}); - -function getLastLockVersion() { - var token = $("meta[name=csrf-token]").attr('content'); - var lock_version = $('#issue_lock_version').val(); - - - jQuery.ajax({ - type: 'GET', - url: LOCATION_HREF, - data: { "authenticity_token" : token }, - crossDomain: true, - async: false, - beforeSend: function(xhr) { - xhr.setRequestHeader("authenticity_token", token); - }, - success: function(msg) { - parsed = $.parseHTML(msg); - lock_version = $(parsed).find('#issue_lock_version').val(); - } - }); - return lock_version; -} - -function issueDynamicUpdate(field_name, field_value, type, cssClass) { - /* hide edit field */ - $('.details .' + cssClass + ' .value').removeClass('edited'); - - /* add spin notification */ - $('.details .' + cssClass + ' .value').append(' '); - - /* update value displayed */ - $('.details .' + cssClass + ' .showValue').html(function() { - if (type == "select") { - return $('.details .' + cssClass + ' .value select option:selected').html() - } else if (type == "input") { - return $('.details .' + cssClass + ' .value input').val() - } else if (type == "textarea") { - return $('.details .' + cssClass + ' .value textarea').val() - } else if (type == "date") { - return "XXXX/XX/XX"; - } - }); - - /* lost focus on element */ - if (type != "select") { - $('.details .' + cssClass + ' .value input').blur(); - } - - var token = $("meta[name=csrf-token]").attr('content'); - - $('#issue-form').find("#issue_" + field_name).val(field_value).css({"display": "inline-block"}); - // avoid conflict revision - var lastLockVersion = getLastLockVersion(); - $('#issue_lock_version').val(lastLockVersion); - var formData = ""; - - // If checkbox we have to uncheck everything in the issue-form and get data from dynamic edit - if(type == "checkbox"){ - formData = field_value + "&"; - var cf_id = field_name.replace(/\D/g,''); - $('input[name=issue\\[custom_field_values\\]\\[' + cf_id + '\\]\\[\\]]').each(function(){ - $(this).prop('checked', false); - }); - } - - formData += $('#issue-form').serialize(); - - - jQuery.ajax({ - type: 'POST', - url: LOCATION_HREF, - data: formData, - beforeSend: function(xhr) { - xhr.setRequestHeader("authenticity_token", token); - }, - success: function(msg) { - /* get result page content (updated issue detail page with new status) */ - - var parsed = $.parseHTML(msg); - - var error = $(parsed).find("#errorExplanation"); - - if (error.length) { - if ($('html').find("#errorExplanation").length == 0) { - $('.issue.details').before("
" + error.html() + "
"); - } else { - $('html').find("#errorExplanation").html(error.html()); - } - - /* data updated, remove spin and add success icon for 2sec */ - setTimeout(function() { - $('.details .' + cssClass + ' i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-times statusKo'); - setTimeout(function() { - $('.details .' + cssClass + ' i.fa-times.statusKo').remove(); - }, 2000); - }, 500); - - - jQuery.ajax({ - type: 'GET', - url: LOCATION_HREF, - data: { "authenticity_token" : token }, - crossDomain: true, - async: false, - beforeSend: function(xhr) { - xhr.setRequestHeader("authenticity_token", token); - }, - success: function(msg) { - parsed = $.parseHTML(msg); - } - }); + // Checkbox specific case + var is_checkboxes = false; + let is_file = false; + let is_list = false; + let CF_ID = false; + if(!formElement.length && attribute.startsWith("custom_field_values_")){ + CF_ID = attribute.split("custom_field_values_")[1]; + /* Is it a checkbox block ? */ + formElement = $('#issue_custom_field_values_' + CF_ID); + if(formElement.length){ + formElement = formElement.parents('.check_box_group'); + is_checkboxes = CF_ID; + } else { + /* Is it a file block ? */ + formElement = $('#issue_custom_field_values_' + CF_ID + '_blank'); + if(formElement.length){ + formElement = formElement.parents('p'); + formElement.find('label').remove(); + is_file = CF_ID; } else { - /* removing error div if exists */ - $('html').find("#errorExplanation").remove(); + /* Is it a checkbox/radio group ? */ + formElement = $('#issue-form .cf_' + CF_ID + '.check_box_group'); + is_list = CF_ID; } - - /* we update the details block */ - $('div.issue.details').html( $(parsed).find('div.issue.details').html() ); - $('body').find('.details .' + cssClass + ' .value').append(' '); - - /* we update form*/ - $('form#issue-form').html( $(parsed).find('form#issue-form').html() ); - - /* we update issue properties edit block */ - $('#all_attributes').html( $(parsed).find('#all_attributes').html() ); - - /* we init edit fields */ - initEditFields(); - initEditFieldListeners(); - - if ($(parsed).find('#required_field_array').length) { - updateRequiredFields(JSON.parse($(parsed).find('#required_field_array').html())); - } - - /* we update the history list */ - $('#history').append($(parsed).find('#history .journal.has-details:last-child')); - - /* data updated, remove spin and add success icon for 2sec */ - setTimeout(function(){ - $('.details .' + cssClass + ' i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk'); - setTimeout(function(){ - $('.details .' + cssClass + ' i.fa-check.statusOk').remove(); - }, 2000); - }, 500); - - //set datepicker fallback for input type date - if ( - $('body').find('input[type=date]').length && - $('body').find('input[type=date]').datepickerFallback instanceof Function && - typeof datepickerOptions !== 'undefined' - ) { - $('body').find('input[type=date]').datepickerFallback(datepickerOptions); - } - }, - error: function(xhr, msg, error) { - /* error and no update, info logged into console */ - console.log('%c -------- Error while updating the issue attribute dynamically -------- ', 'background: #ff0000; color: white; font-weight:900'); - console.log('%c xhr data: ', 'background: black; color: white;'); - console.log(xhr); - console.log('%c msg data: ', 'background: black; color: white;'); - console.log(msg); - console.log('%c error data: ', 'background: black; color: white;');; - console.log(error); - console.log('%c ---------------------------------------------------------- ', 'background: #ff0000; color: white; font-weight:900'); - - $('.details .' + cssClass + ' i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-times').html(" " + _TXT_ERROR_AJAX_CALL); - setTimeout(function(){ - $('.details .' + cssClass + ' i.fa-times').remove(); - }, 2000); } - }); -}; - -/* Listeners foreach attribute */ -function initEditFieldListeners() { - 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-' + domSelectPriorities.val()); - }); /* end on change domSelectPriorities */ - - var domSelectCategories = $('body').find('#categoriesListDropdown select'); - domSelectCategories.on('change', function(e){ - issueDynamicUpdate('category_id', domSelectCategories.val(), 'select', 'category'); - - /* update the classes priority from */ - $("#content > div.issue").removeClass(function (index, className) { - return (className.match (/(^|\s)priority-\S+/g) || []).join(' '); - }).addClass('category-' + domSelectPriorities.val()); - }); /* end on change domSelectCategories */ - - var domSelectUsers = $('body').find('#usersListDropdown select'); - domSelectUsers.on('change', function(e){ - issueDynamicUpdate('assigned_to_id', domSelectUsers.val(), 'select', 'assigned-to'); - }); /* end on change domSelectUsers */ - - var domSelectRatio = $('body').find('#doneRatioListDropdown select'); - domSelectRatio.on('change', function(e){ - issueDynamicUpdate('done_ratio', domSelectRatio.val(), 'progress', 'progress'); - }); /* end on change domSelectRatio */ - - var domInputEstimatedTime = $('body').find('#EstimatedTimeInput input'); - $('#EstimatedTimeInput a.btn.validate').on('click', function(e) { - e.preventDefault(); - $('.estimated-hours .value .error').remove(); - var estimatedTime = parseFloat(domInputEstimatedTime.val()); - - if (estimatedTime >= 0) { - issueDynamicUpdate('estimated_hours', estimatedTime, 'input', 'estimated-hours'); + } + + if(formElement.length){ + var clone = formElement.clone(); + if(clone.is('select') && !clone.prop('multiple')) clone.on('change', function(e){sendData($(this).serializeArray());}); + if(is_checkboxes || is_file || is_list) { + clone.prop('id', "issue_custom_field_values_" + CF_ID + "_dynamic"); } else { - /* estimated time must be > 0 */ - $('.estimated-hours .value').append(' ' + _TXT_ERROR_POSITIVE_NUMBER + ''); + clone.prop('id', formElement.prop('id') + "_dynamic"); } + var wrapper = $("
").addClass('dynamicEditField'); + wrapper.append(clone); + if(!clone.is('select') || clone.prop('multiple')) wrapper.append(""); + wrapper.append(""); + return wrapper; + } - return false; - }); + return null; +} - domInputEstimatedTime.on('keyup', function(e){ - $('.details .attributes .estimated-hours.attribute .selectedValue span').html( - $('.details .attributes .estimated-hours.attribute .value input').val() - ); +/* Loop over all form attribute and clone them into details part */ +var cloneEditForm = function(){ + $('.issue.details .subject').append(''); + $(".issue.details ").wrap("
"); - if (e.keyCode == 13) { - $('#EstimatedTimeInput a.btn.validate').click(); + $('div.issue.details .attribute').each(function(){ + var classList = $(this).attr('class').split(/\s+/); + + var attributes = classList.filter(function(elem) { return elem != "attribute"; }); + // Specific case : all "-" are replaced by "_" into form id + attributes = attributes.map(attr => attr.replaceAll('-', '_')); + + let custom_field = false; + attributes.forEach(function(part, index, arr) { + if(arr[index] === "progress") arr[index] = "done_ratio"; + if(arr[index].startsWith('cf_')) { + arr[index] = arr[index].replace('cf', 'custom_field_values'); + custom_field = arr[index]; + } + }); + + attributes = attributes.join(" "); + + let selected_elt = custom_field ? custom_field : attributes; + if(attributes && !_CONF_EXCLUDED_FIELD_ID.includes(selected_elt)){ + let dynamicEditField = getEditFormHTML(selected_elt); + if(dynamicEditField) $(this).find('.value').append(" " + SVG_EDIT + "").append(dynamicEditField); } - });/* end EstimatedTime */ + }); - var domInputStartDate = $('body').find('#StartDateInput input'); - $('#StartDateInput a.btn.validate').on('click', function(e) { - e.preventDefault(); - $('.start-date .value .error').remove(); - if (new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime() || $('body').find('#DueDateInput input').val() == "") - { - issueDynamicUpdate('start_date', domInputStartDate.val(), 'date', 'start-date'); - } else { - /* start date must be < due date */ - $('.start-date .value').append(' ' + _TXT_ERROR_START_DATE + ''); - } + // Specific Case : Description field + if(!_CONF_EXCLUDED_FIELD_ID.includes("description") && document.querySelectorAll('div.issue.details .description').length){ + $('div.issue.details .description > p').first().find('strong').after(" " + SVG_EDIT + ""); + var formDescription = getEditFormHTML("description"); + formDescription.find("#issue_description_dynamic").removeAttr('data-tribute'); + $('div.issue.details .description').append(formDescription); - return false; - }); - - domInputStartDate.on('keyup', function(e){ - if (e.keyCode == 13) { - $('#StartDateInput a.btn.validate').click(); - } - });/* end StartDate */ - - var domInputDueDate = $('body').find('#DueDateInput input'); - $('#DueDateInput a.btn.validate').on('click', function(e) { - e.preventDefault(); - $('.due-date .value .error').remove(); - - if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime() || $('body').find('#StartDateInput input').val() == "" ) - { - issueDynamicUpdate('due_date', domInputDueDate.val(), 'date', 'due-date'); - } else { - /* start date must be < due date */ - $('.due-date .value').append(' ' + _TXT_ERROR_DUE_DATE + ''); - } - return false; - }); - - domInputDueDate.on('keyup', function(e){ - if (e.keyCode == 13) { - $('#DueDateInput a.btn.validate').click(); - } - });/* end StartDate */ - - var domInputTitle = $('body').find('#TitleInput input'); - $('#TitleInput a.btn.validate').on('click', function(e) { - e.preventDefault(); - issueDynamicUpdate('subject', domInputTitle.val(), 'input', 'subject'); - - return false; - }); - - domInputTitle.on('keyup', function(e){ - if (e.keyCode == 13) { - $('#TitleInput a.btn.validate').click(); - } - });/* end Title */ - - var domInputDescription = $('body').find('#DescriptionInput textarea'); - - if(domInputDescription.length) { - - if ( + if ( typeof(CKEDITOR) === "object" && + typeof(CKEDITOR.instances['issue_description'] !== "undefined") && typeof(CKEDITOR.instances['issue_description'].getData) === typeof(Function) ) { var cfg = CKEDITOR.instances['issue_description'].config; cfg.height = 100; - CKEDITOR.replace("description_textarea", cfg) + CKEDITOR.replace("issue_description_dynamic", cfg) }else if (typeof(jsToolBar) === typeof(Function)) { - var wikiToolbar = new jsToolBar(document.getElementById('description_textarea')); wikiToolbar.draw(); + var wikiToolbar = new jsToolBar(document.getElementById('issue_description_dynamic')); wikiToolbar.draw(); } + } - $('#DescriptionInput a.btn.validate').on('click', function(e) { - e.preventDefault(); - var new_value = domInputDescription.val(); + // Specific Case : Title field + if(!_CONF_EXCLUDED_FIELD_ID.includes("subject")){ + $('div.issue.details .subject h3').append(" " + SVG_EDIT + ""); + var formTitle = getEditFormHTML("issue_subject"); + $('div.issue.details .subject').append(formTitle); + } +} - if ( - typeof(CKEDITOR) === "object" && - typeof(CKEDITOR.instances['description_textarea'].getData) === typeof(Function) - ) { - new_value = CKEDITOR.instances['description_textarea'].getData(); +/* Perform action on .value (display edit form) */ +$('body').on(_CONF_LISTENER_TYPE_VALUE, + 'div.issue.details .attributes .attribute .' + _CONF_LISTENER_TARGET + ', div.issue.details div.description > p, div.issue.details div.subject', + function(e){ + if($(e.target).closest('.dynamicEditField').length) return; /* We're already into a dynamic field, ignore */ + $('.dynamicEditField').each(function(e){ $(this).removeClass('open'); }); + if(!$(e.target).closest('a').length && !$(e.target).closest('button').length){ + if($(this).parent().hasClass('description')){ + $(this).parent().find('.dynamicEditField').addClass('open'); + } else { + $(this).find('.dynamicEditField').addClass('open'); } + } +}); - issueDynamicUpdate('description', new_value, 'textarea', 'description'); +/* Perform action on .iconEdit (display edit form) */ +$('body').on(_CONF_LISTENER_TYPE_ICON, + 'div.issue.details .iconEdit', function(e){ + $('.dynamicEditField').each(function(e){ $(this).removeClass('open'); }); + $(this).parent().find('.dynamicEditField').addClass('open'); +}); - return false; - }); - } +/* Perform data update when clicking on valid button from edit form */ +$('body').on('click', '.dynamicEditField .action.valid', function(e){ + e.preventDefault(); + var input = $(this).parents('.dynamicEditField').find(':input'); + sendData(input.serializeArray()); + $(this).parents('.dynamicEditField').removeClass('open'); +}); - var dynamic_edit_assigned_to_id = $('body').find('#dynamic_edit_assigned_to_id select'); +/* Hide edit form when clicking on cancel button */ +$('body').on('click', '.dynamicEditField .action.refuse', function(e){ + e.preventDefault(); + $(this).parents('.dynamicEditField').removeClass('open'); +}); - if (dynamic_edit_assigned_to_id.length) { - dynamic_edit_assigned_to_id.on('change', function(e){ - issueDynamicUpdate('assigned_to_id', dynamic_edit_assigned_to_id.val(), 'select', 'assigned-to'); - }); - } +/* Update whole .details block + history + form with global refresh button */ +$('body').on('click', '.refreshData', function(e){ + sendData(); +}); - var dynamic_edit_fixed_version = $('body').find('#dynamic_edit_fixed_version select'); +/* Listen on esc key press to close opened dialog box */ +document.onkeydown = function(evt) { + evt = evt || window.event; + var isEscape = false; + if ("key" in evt) { + isEscape = (evt.key === "Escape" || evt.key === "Esc"); + } else { + isEscape = (evt.keyCode === 27); + } + if (isEscape) { + $('.dynamicEditField').each(function(e){ $(this).removeClass('open'); }); + } +}; - if (dynamic_edit_fixed_version.length) { - dynamic_edit_fixed_version.on('change', function(e){ - issueDynamicUpdate('fixed_version_id', dynamic_edit_fixed_version.val(), 'select', 'fixed-version'); - }); - } - - /* end Description */ - - /* Custom fields */ - for (var i = 0 ; i < CF_VALUE_JSON.length ; i++) { - ( - function() { - var info = CF_VALUE_JSON[i].custom_field; - var value = CF_VALUE_JSON[i].value; - - if (info.visible && info.editable) { - var inputType = "input"; - switch (info.field_format) { - case "bool": - case "user": - case "list": - case "enumeration": - case "version": - inputType = "select"; - break; - case "text": - inputType = "textarea"; - break; - } - - if(info.format_store.edit_tag_style == "check_box"){ - inputType = "checkbox"; - } - - var domInputField = $('body').find('#dynamic_issue_custom_field_values_' + info.id); - $('body').find('#dynamic_edit_cf_' + info.id + ' a.btn.validate').on('click', function(e) { - var new_value = domInputField.val(); - - // Specific case with checkboxes - if(typeof new_value === 'undefined'){ - var new_value = $('body').find('#dynamic_edit_cf_' + info.id + " :input").serialize(); - } - - issueDynamicUpdate('custom_field_values_' + info.id , new_value, inputType, 'cf_' + info.id); - - return false; - }); - - domInputField.on('keyup', function(e) { - if (e.keyCode == 13 && inputType != "textarea") { - $('body').find('#dynamic_edit_cf_' + info.id + ' a.btn.validate').click(); +let checkVersion = function(callback){ + jQuery.ajax({ + type: 'GET', + url: LOCATION_HREF, + crossDomain: true, + async: false, + success: function(msg) { + let parsed = $.parseHTML(msg); + let current_version = $(parsed).find('#issue_lock_version').val(); + + if(current_version !== $('#issue_lock_version').val()){ + if(!$('#content .conflict').length){ + $('#content').prepend(` +
+ ${_TXT_CONFLICT_TITLE} +
+
+

${_TXT_CONFLICT_TXT}

+
+
+
`); + } + } else { + $('#content .conflict').remove(); } - }); - } - }() - ); // closure FTW + + if(callback) callback(current_version); + } + }); +} + +let checkVersionInterval = false; +let setCheckVersionInterval = function(activate){ + if(!_CONF_CHECK_ISSUE_UPDATE_CONFLICT) return false; + if(activate && !checkVersionInterval){ + checkVersionInterval = window.setInterval(function(){ checkVersion(); }, 5000); + } else { + clearInterval(checkVersionInterval); + checkVersionInterval = false; } } -initEditFieldListeners(); +setCheckVersionInterval(true); + +/* Global function to perform AJAX call */ +var sendData = function(serialized_data){ + + let updateIssue = function(serialized_data){ + setCheckVersionInterval(false); + var token = $("meta[name=csrf-token]").attr('content'); + var params = serialized_data || []; + params.push({name: '_method', value: "patch"}); + params.push({name: 'authenticity_token', value: token}) + + jQuery.ajax({ + type: 'POST', + url: LOCATION_HREF, + data: $.param(params), + success: function(msg) { + /* get result page content (updated issue detail page with new status) */ + $('#ajax-indicator').css('display', 'none'); + + var parsed = $.parseHTML(msg); + + var error = $(parsed).find("#errorExplanation"); + if (error.length) { + + if ($('html').find("#errorExplanation").length == 0) { + $('.issue.details').before("
" + error.html() + "
"); + + $([document.documentElement, document.body]).animate({ + scrollTop: $("#errorExplanation").offset().top + }, 500); + } else { + $('html').find("#errorExplanation").html(error.html()); + } + + jQuery.ajax({ + type: 'GET', + url: LOCATION_HREF, + data: { "authenticity_token" : token }, + crossDomain: true, + async: false, + success: function(msg) { + parsed = $.parseHTML(msg); + } + }); + } else { + /* removing error div if exists */ + $('html').find("#errorExplanation").remove(); + } + + /* we update form*/ + $('form#issue-form').html( $(parsed).find('form#issue-form').html() ); + + /* we update issue properties edit block */ + $('#all_attributes').html( $(parsed).find('#all_attributes').html() ); + + /* we update the details block */ + $('div.issue.details').html( $(parsed).find('div.issue.details').html() ); + + /* we update the history list */ + $('#tab-content-history').append($(parsed).find('#history .journal.has-details:last-child')); + + /* we init edit fields */ + cloneEditForm(); + + //set datepicker fallback for input type date + if ( + $('body').find('input[type=date]').length && + $('body').find('input[type=date]').datepickerFallback instanceof Function && + typeof datepickerOptions !== 'undefined' + ) { + $('body').find('input[type=date]').datepickerFallback(datepickerOptions); + } + + setCheckVersionInterval(true); + }, + error: function(xhr, msg, error) { + setCheckVersionInterval(true); + $('#ajax-indicator').css('display', 'none'); + + /* error and no update, info logged into console */ + console.groupCollapsed('%c -------- Error while updating the issue attribute dynamically -------- ', 'background: #ff0000; color: white; font-weight:900'); + console.log("POST " + LOCATION_HREF); + console.table(params); + console.log('%c xhr data: ', 'background: black; color: white;'); + console.log(xhr); + console.log('%c msg data: ', 'background: black; color: white;'); + console.log(msg); + console.log('%c error data: ', 'background: black; color: white;');; + console.log(error); + console.groupEnd(); + } + }); + }; + + if(_CONF_CHECK_ISSUE_UPDATE_CONFLICT){ + checkVersion(function(current_version){ + if(current_version == $('#issue_lock_version').val()){ + updateIssue(serialized_data); + } else { + $([document.documentElement, document.body]).animate({ + scrollTop: $("#content .conflict").offset().top + }, 500); + } + }); + } else { + updateIssue(serialized_data); + } +} + +// Init plugin +cloneEditForm(); \ No newline at end of file diff --git a/assets/javascripts/issue_dynamic_edit_configuration_file.js b/assets/javascripts/issue_dynamic_edit_configuration_file.js index 301b723..8b2c754 100644 --- a/assets/javascripts/issue_dynamic_edit_configuration_file.js +++ b/assets/javascripts/issue_dynamic_edit_configuration_file.js @@ -36,15 +36,25 @@ var _CONF_LISTENER_TYPE_ICON = "click"; /* * _CONF_LISTENER_TARGET (string) * Choose which area will trigger the apparition of the edition block - * "value" will target the whole line, "fa-pencil" will only target the pencil icon - * Allowed values : value (default), fa-pencil + * "value" will target the value of the attribute (+ pencil), "iconEdit" will only target the pencil icon, "label" will trigger the label attribute + * "all" will target the whole line (label + value + pencil) + * Allowed values : value (default), iconEdit, label, all */ var _CONF_LISTENER_TARGET = "value"; /* * _CONF_EXCLUDED_FIELD_ID (string array) * Choose which fields to exclude. They won't have the edit block and pencil - * Custom fields have an unique ID and this ID must be prefixed by "issue_custom_field_values_". Eg : "issue_custom_field_values_4" is an allowed value - * Allowed values : array of any ID selector (css). Eg : ["statusListDropdown", "StartDateInput", "TitleInput", "issue_custom_field_values_4"] + * You have to take element (input, select, textarea ...) class attribute from edit form at the bottom of the page + * Custom fields have an unique ID and this ID must be prefixed by "custom_field_values_". Eg : "custom_field_values_4" is an allowed value + * Allowed values : array of any ID selector (css). Eg : ["status", "priority", "category", "assigned_to", "done_ratio", "start_date", "custom_field_values_4"] */ var _CONF_EXCLUDED_FIELD_ID = []; + +/* + * _CONF_CHECK_ISSUE_UPDATE_CONFLICT (boolean) + * Choose if you allow current user to override all modifications performed by other users while editing the issue + * true : will check issue update conflict and prevent current user to update the issue without refreshing the page + * false : user will be able to update the issue no matter other modification performed (will override modification made by other) + */ +var _CONF_CHECK_ISSUE_UPDATE_CONFLICT = true; diff --git a/assets/stylesheets/issue_dynamic_edit.css b/assets/stylesheets/issue_dynamic_edit.css index c1afc79..5096212 100644 --- a/assets/stylesheets/issue_dynamic_edit.css +++ b/assets/stylesheets/issue_dynamic_edit.css @@ -1,17 +1,20 @@ /* prefix selector with body.controller-issues.action-show to avoid unwanted style on other page ? */ -body.controller-issues.action-show div.issue.details .value, body.controller-issues.action-show div.issue.details .splitcontent { +body.controller-issues.action-show div.issue.details .value, body.controller-issues.action-show div.issue.details .attributes .attribute .value { overflow: visible; position: relative; + cursor: pointer; + display: flex; + align-items: center; +} + +body.controller-issues.action-show .issue.details div.description, +body.controller-issues.action-show .issue.details div.subject { + position: relative; } -body.controller-issues.action-show div.issue .attribute .value .percent { - display: inline-block; -} - -/* Progress bar fix */ -body.controller-issues.action-show table.progress { - margin-right: 10px; +body.controller-issues.action-show .issue.details.no-cursor .value { + cursor: initial; } body.controller-issues.action-show .attribute .error { @@ -19,28 +22,39 @@ body.controller-issues.action-show .attribute .error { margin-left: 3px; } -body.controller-issues.action-show .fa-pencil { +body.controller-issues.action-show div.issue.details .subject { + display: flex; + justify-content: space-between; + align-items: center; +} + +body.controller-issues.action-show div.issue.details .subject .refreshData { + cursor: pointer; +} + +body.controller-issues.action-show div.issue.details .splitcontent { + overflow: initial; +} + +body.controller-issues.action-show div.issue.details .iconEdit { opacity: 0; + display: inline-block; + transition: opacity .3s ease-in; + font-weight: 700; } -body.controller-issues.action-show .issue.details .showValue { - cursor: pointer; -} - -body.controller-issues.action-show .issue.details.no-cursor .showValue { - cursor: initial; -} - -body.controller-issues.action-show .issue.details .value:hover .fa-pencil, -body.controller-issues.action-show .issue.details.showDynamicEdit:hover .value .fa-pencil { +body.controller-issues.action-show div.issue.details.showPencil .iconEdit, +body.controller-issues.action-show div.issue.details .attribute:hover .iconEdit, +body.controller-issues.action-show div.issue.details .description:hover .iconEdit, +body.controller-issues.action-show div.issue.details .subject:hover .iconEdit { opacity: 1; - cursor: pointer; } -body.controller-issues.action-show .dynamicEdit { +body.controller-issues.action-show .dynamicEditField { display: inline-block; position:absolute; opacity: 0; + transition: opacity .3s ease-in; /* left:0; correct position on top of icon */ bottom: 100%; margin-bottom: 5px; @@ -54,39 +68,36 @@ body.controller-issues.action-show .dynamicEdit { white-space: nowrap; /* force icons to stay on the same line */ } -body.controller-issues.action-show #TitleInput.dynamicEdit { - font-size: 0.57em; +body.controller-issues.action-show .subject .dynamicEditField { + bottom: initial; + top:100%; } -body.controller-issues.action-show .dynamicEdit.version { - max-width: 100%; -} - -body.controller-issues.action-show .edited .dynamicEdit { +body.controller-issues.action-show .dynamicEditField.open { + opacity: 1; + z-index: 999; pointer-events: auto; } -body.controller-issues.action-show div.issue.details .value.edited .dynamicEdit { - opacity: 1; - z-index: 999; +body.controller-issues.action-show #TitleInput.dynamicEditField { + font-size: 0.57em; } -body.controller-issues.action-show div.issue.details .btn-primary { - color: white !important; - border-radius: 3px; - padding: 3px; - vertical-align: middle; +body.controller-issues.action-show .dynamicEditField button.action { + margin-left: .5em; + cursor: pointer; + border: 0; + background-color: #efefef; + line-height: 0; } -body.controller-issues.action-show div.issue.details .btn-primary.close { - background: #c0392b; +body.controller-issues.action-show .dynamicEditField button.action.valid { + font-weight: 700; + background-color: #27ae60; + color: white; } -body.controller-issues.action-show div.issue.details .btn-primary.validate { - background: #27ae60; -} - -body.controller-issues.action-show .dynamicEdit select { +body.controller-issues.action-show .dynamicEditField select { border:none; background-image:none; background-color:transparent; @@ -99,41 +110,77 @@ body.controller-issues.action-show .dynamicEdit select { border-radius: 3px; } -body.controller-issues.action-show .dynamicEdit select option[disabled="disabled"] -{ +body.controller-issues.action-show .dynamicEditField select option[disabled="disabled"] { display:none; } -body.controller-issues.action-show div.issue.details .attributes { - display: table; - width: 100%; -} - -body.controller-issues.action-show div.issue.details .splitcontent { - display: table-row; -} - -div.issue div.subject h3 { +body.controller-issues.action-show div.issue div.subject h3 { position:relative; display: inline-block; + cursor: pointer; } -body.controller-issues.action-show .dynamicEdit input[type="text"] { +body.controller-issues.action-show .dynamicEditField input[type="text"] { width: auto !important; } -body.controller-issues.action-show .dynamicEdit textarea { +body.controller-issues.action-show .dynamicEditField textarea { width: 100%; display: block; margin-bottom: 10px; } -body.controller-issues.action-show .dynamicEdit input, -body.controller-issues.action-show .dynamicEdit select { +body.controller-issues.action-show .dynamicEditField input, +body.controller-issues.action-show .dynamicEditField select { vertical-align: middle; } -body.controller-issues.action-show .dynamicEdit .check_box_group { +body.controller-issues.action-show .dynamicEditField .check_box_group { border: 0px !important; margin-bottom: 10px; } + +/* MOBILE MEDIAQUERY */ +@media screen and (max-width: 899px){ + + body.controller-issues.action-show div.issue.details .subject { + display: block; + } + + body.controller-issues.action-show div.issue.details .subject .refreshData { + display: none; + } + + body.controller-issues.action-show div.issue.details .value, body.controller-issues.action-show div.issue.details .attributes .attribute .value { + display: block; + } + + body.controller-issues.action-show .dynamicEditField { + display: block; + position: relative; + margin: 10px 0; + white-space: normal; + height: 0; + overflow: hidden; + padding: 0; + } + + body.controller-issues.action-show .dynamicEditField.open { + height: auto; + padding: 10px; + } +} + +/* PRINT MEDIAQUERY */ +@media print { + body.controller-issues.action-show div.issue.details .subject .refreshData, + body.controller-issues.action-show div.issue.details .iconEdit, + body.controller-issues.action-show .dynamicEditField { + display : none !important; + height: 0; + width: 0; + overflow: hidden; + padding : 0; + margin: 0; + } +} \ No newline at end of file diff --git a/config/locales/de.yml b/config/locales/de.yml deleted file mode 100644 index b1c9838..0000000 --- a/config/locales/de.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Translation by Peter Pfläging -de: - ide_txt_validation_btn : 'Änderung bestätitgen' - ide_txt_error_positive_number : 'Hier muss eine positive Zahl stehen' - ide_txt_error_start_date : 'Das Startdatum muss vor dem Enddatum liegen' - ide_txt_error_due_date : 'Das Enddatum muss nach dem Startdatum liegen' - ide_txt_error_ajax_call : 'Error (Javascript Konsole kontrollieren)' - ide_txt_cancel_btn : 'Änderungen ignorieren' - ide_txt_required_field : 'Pflichtfeld bitte ausfüllen' diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b0e145..2c636dd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,8 +1,3 @@ -en: - ide_txt_validation_btn : 'Validate the modification' - 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)' - ide_txt_cancel_btn : 'Cancel the modification' - ide_txt_required_field : 'This field is required to go to next step' \ No newline at end of file +en: + ide_txt_notice_conflict_title : "This issue has been updated by someone else while you are editing it." + ide_txt_notice_conflict_text : "Refresh the current page to get the updated data. Your current modifications will be lost" \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b727ae4..20d7a51 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1,8 +1,3 @@ -fr: - ide_txt_validation_btn : 'Valider la modification' - 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)' - ide_txt_cancel_btn : 'Annuler la modification' - ide_txt_required_field : 'Ce champs est obligatoire pour passer au Statut suivant' \ No newline at end of file +fr: + ide_txt_notice_conflict_title : "La demande a été mise à jour pendant que vous la modifiez." + ide_txt_notice_conflict_text : "Rafraichissez la page pour voir une version à jour de la demande. Vos modifications actuelles seront perdues" \ No newline at end of file diff --git a/init.rb b/init.rb index a5cbc8c..31fce4d 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 without refreshing the page (JIRA style)' - version '0.7.2' + version '0.8.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 a0eb12a..f416ed5 100644 --- a/lib/details_issue_hooks.rb +++ b/lib/details_issue_hooks.rb @@ -24,238 +24,14 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener end end - def view_issues_show_details_bottom(context = {}) - project = context[:project] - request = context[:request] - issue_id = request.path_parameters[:id] - back = request.env['HTTP_REFERER'] - - o = '' - - if issue_id - issue = Issue.find(issue_id) - readOnlyAttributes = issue.read_only_attribute_names(User.current) - requiredAttributes = issue.required_attribute_names(User.current) - - # o << requiredAttributes.to_json - - allRequiredFieldsFilled = true - requiredAttributes.each do |attr| - if issue.read_attribute(attr).to_s.empty? - allRequiredFieldsFilled = false - end - end - - if issue - if User.current.allowed_to?(:edit_issues, project) - # 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? && - !readOnlyAttributes.include?('status_id') && - allRequiredFieldsFilled - ) - o << "" - o << "" - o << " " - o << "" - end - - # Users dropdown - assignables = project.assignable_users - if ( - !assignables.empty? && - !readOnlyAttributes.include?('assigned_to_id') - ) - o << "" - o << "" - o << " " - o << "" - end - - # Priorities dropdown - priorities = IssuePriority.all - - if !priorities.empty? && !(readOnlyAttributes.include? 'priority_id') - o << "" - o << "" - o << " " - o << "" - end - - # Categories dropdown - categories = project.issue_categories - - if !categories.empty? && !(readOnlyAttributes.include? 'category_id') - o << "" - o << "" - o << " " - o << "" - end - - # %done dropdown - if ! readOnlyAttributes.include?('done_ratio') - percent = 0 - o << "" - o << "" - o << " " - o << "" - end - - # Estimated_time dropdown - if ! readOnlyAttributes.include?('estimated_hours') - o << "" - o << " " - o << "" - o << " " - o << "" - end - - # Start date - if ! readOnlyAttributes.include?('start_date') - o << "" - o << " " - o << " " - o << " " - o << "" - o << "" - end - - # Due date - if ! readOnlyAttributes.include?('due_date') - o << "" - o << " " - o << " " - o << " " - o << "" - o << "" - end - - # Title - # Make quotings in subject! (PP) - clonesubject = issue.subject.gsub('"','"') - o << "" - o << " " - o << " " - o << " " - o << "" - o << "" - - # Description - o << "" - o << " " - o << "
" - o << "
" - o << "
" - - # JS Part at the end of the edit block - o << "\n" - - o << "\n" - - # closing the display none div parent - o << "
" - end - end - - return o - + def view_issues_show_details_bottom(context) + if User.current.allowed_to?(:edit_issues, context[:project]) + content = "\n" + return content.html_safe end end + end