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("