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)

This commit is contained in:
Hugo 2021-12-29 18:34:55 +01:00
parent e968ee7499
commit ad37b96b53
9 changed files with 439 additions and 974 deletions

View File

@ -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)

View File

@ -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 = '<svg style="width: 1em; height: 1em;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="svg_edit"><path d="m2 20c0 1.1.9 2 2 2h2.6l-4.6-4.6z"/><path d="m21.6 5.6-3.2-3.2c-.8-.8-2-.8-2.8 0l-.2.2c-.4.4-.4 1 0 1.4l4.6 4.6c.4.4 1 .4 1.4 0l.2-.2c.8-.8.8-2 0-2.8z"/><path d="m14 5.4c-.4-.4-1-.4-1.4 0l-9.1 9.1c-.5.5-.5 1.1-.1 1.5l4.6 4.6c.4.4 1 .4 1.4 0l9.1-9.1c.4-.4.4-1 0-1.4z"/></g></svg>';
var SVG_VALID = '<svg style="width: 1em; height: 1em; fill:white;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="info"/><g id="icons"><path d="M10,18c-0.5,0-1-0.2-1.4-0.6l-4-4c-0.8-0.8-0.8-2,0-2.8c0.8-0.8,2.1-0.8,2.8,0l2.6,2.6l6.6-6.6 c0.8-0.8,2-0.8,2.8,0c0.8,0.8,0.8,2,0,2.8l-8,8C11,17.8,10.5,18,10,18z" class="svg_check"/></g></svg>';
var SVG_CANCEL = '<svg style="width: 1em; height: 1em;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="info"/><g id="icons"><path d="M14.8,12l3.6-3.6c0.8-0.8,0.8-2,0-2.8c-0.8-0.8-2-0.8-2.8,0L12,9.2L8.4,5.6c-0.8-0.8-2-0.8-2.8,0 c-0.8,0.8-0.8,2,0,2.8L9.2,12l-3.6,3.6c-0.8,0.8-0.8,2,0,2.8C6,18.8,6.5,19,7,19s1-0.2,1.4-0.6l3.6-3.6l3.6,3.6 C16,18.8,16.5,19,17,19s1-0.2,1.4-0.6c0.8-0.8,0.8-2,0-2.8L14.8,12z" class="svg_cancel"/></g></svg>';
/*
* 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 +
'<span class="showValue">' +
$('.details .attributes .status.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#prioritiesListDropdown').length > 0 && !isExcluded('prioritiesListDropdown')) {
var htmlCopy = $('#prioritiesListDropdown').get(0).outerHTML;
$('#prioritiesListDropdown').remove();
$('.details .attributes .priority.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .priority.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#categoriesListDropdown').length > 0 && !isExcluded('categoriesListDropdown')) {
var htmlCopy = $('#categoriesListDropdown').get(0).outerHTML;
$('#categoriesListDropdown').remove();
$('.details .attributes .category.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .category.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#doneRatioListDropdown').length > 0 && !isExcluded('doneRatioListDropdown')) {
var htmlCopy = $('#doneRatioListDropdown').get(0).outerHTML;
$('#doneRatioListDropdown').remove();
$('.details .attributes .progress.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .progress.attribute .value').html() + '</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#EstimatedTimeInput').length > 0 && !isExcluded('EstimatedTimeInput')) {
var htmlCopy = $('#EstimatedTimeInput').get(0).outerHTML;
$('#EstimatedTimeInput').remove();
$('.details .attributes .estimated-hours.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .estimated-hours.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#StartDateInput').length > 0 && !isExcluded('StartDateInput')) {
var htmlCopy = $('#StartDateInput').get(0).outerHTML;
$('#StartDateInput').remove();
$('.details .attributes .start-date.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .start-date.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#DueDateInput').length > 0 && !isExcluded('DueDateInput')) {
var htmlCopy = $('#DueDateInput').get(0).outerHTML;
$('#DueDateInput').remove();
$('.details .attributes .due-date.attribute .value').html(
htmlCopy +
'<span class="showValue">' +
$('.details .attributes .due-date.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
if ($('#TitleInput').length > 0 && !isExcluded('TitleInput')) {
var htmlCopy = $('#TitleInput').get(0).outerHTML;
$('#TitleInput').remove();
$('.subject h3').html(
htmlCopy +
'<span class="showValue">' +
$('.subject h3').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
).addClass('value');
}
if ($('#DescriptionInput').length > 0 && !isExcluded('DescriptionInput')) {
var htmlCopy = $('#DescriptionInput').get(0).outerHTML;
$('#DescriptionInput').remove();
$('div.description .wiki').html(
htmlCopy +
' <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true" style="float:right;"></i><span class="showValue">' +
$('div.description .wiki').html() + '</span>'
).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 = "<span class='dynamicEdit' id='dynamic_edit_assigned_to_id'>";
editHTML += htmlCopy;
editHTML += " <a href='#' class='btn btn-primary close' aria-label='" + _TXT_CANCEL_BTN + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>";
editHTML += "</span>";
$('.details .attributes .assigned-to.attribute .value').html(
editHTML +
'<span class="showValue">' +
$('.details .attributes .assigned-to.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
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 = "<span class='dynamicEdit' id='dynamic_edit_fixed_version'>";
editHTML += htmlCopy;
editHTML += " <a href='#' class='btn btn-primary close' aria-label='" + _TXT_CANCEL_BTN + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>";
editHTML += "</span>";
$('.details .attributes .fixed-version.attribute .value').html(
editHTML +
'<span class="showValue">' +
$('.details .attributes .fixed-version.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
}
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 = "<span class='dynamicEdit " + info.field_format + "' id='dynamic_edit_cf_" + info.id + "'>";
editHTML += htmlCopy;
editHTML += " <a href='#' class='btn btn-primary validate' aria-label='" + _TXT_VALIDATION_BTN + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>";
editHTML += " <a href='#' class='btn btn-primary close' aria-label='" + _TXT_CANCEL_BTN + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>";
editHTML += "</span>";
$('.details .attributes .cf_' + info.id + '.attribute .value').html(
editHTML +
'<span class="showValue">' +
$('.details .attributes .cf_' + info.id + '.attribute .value').html() +
'</span> <i class="fa fa-pencil dynamicEditIcon fa-fw" aria-hidden="true"></i>'
);
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(
'<span title=\"' + _TXT_REQUIRED_FIELD + '\" class=\"field-description\">' +
$('.issue.details .attribute.' + htmlLabel + ' .label').html() +
'</span> <span class=\"required\"> *</span>'
);
}
}
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(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
/* 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("<div id='errorExplanation'>" + error.html() + "</div>");
} 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(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
/* 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('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_POSITIVE_NUMBER + '</span>');
clone.prop('id', formElement.prop('id') + "_dynamic");
}
var wrapper = $("<div/>").addClass('dynamicEditField');
wrapper.append(clone);
if(!clone.is('select') || clone.prop('multiple')) wrapper.append("<button class='action valid'><!--&check;-->" + SVG_VALID + "</button>");
wrapper.append("<button class='action refuse'><!--&#x2715;-->" + SVG_CANCEL + "</button>");
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('<button class="refreshData">&#10227;</button>');
$(".issue.details ").wrap("<form id='fakeDynamicForm'>");
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("&nbsp;<span class='iconEdit'><!--&#9998;-->" + SVG_EDIT + "</span>").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('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_START_DATE + '</span>');
}
// 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("&nbsp;<span class='iconEdit'><!--&#9998;-->" + SVG_EDIT + "</span>");
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('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_DUE_DATE + '</span>');
}
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("&nbsp;<span class='iconEdit'><!--&#9998;-->" + SVG_EDIT + "</span>");
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(`
<div class="conflict">
${_TXT_CONFLICT_TITLE}
<div class="conflict-details">
<div class="conflict-journal">
<p>${_TXT_CONFLICT_TXT}</p>
</div>
</div>
</div>`);
}
} 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("<div id='errorExplanation'>" + error.html() + "</div>");
$([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();

View File

@ -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;

View File

@ -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;
}
}

View File

@ -1,9 +0,0 @@
# Translation by Peter Pfläging <peter@pflaeging.net>
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'

View File

@ -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'
en:
ide_txt_notice_conflict_title : "This issue has been updated by someone else while you are editing it."
ide_txt_notice_conflict_text : "<a href='#' class='refreshData'>Refresh the current page</a> to get the updated data. <strong>Your current modifications will be lost</strong>"

View File

@ -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'
fr:
ide_txt_notice_conflict_title : "La demande a été mise à jour pendant que vous la modifiez."
ide_txt_notice_conflict_text : "<a href='#' class='refreshData'>Rafraichissez la page</a> pour voir une version à jour de la demande. <strong>Vos modifications actuelles seront perdues</strong>"

View File

@ -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

View File

@ -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 << '<div style="display:none">'
# Status dropdown
statuses = issue.new_statuses_allowed_to(User.current)
if (
!statuses.empty? &&
!readOnlyAttributes.include?('status_id') &&
allRequiredFieldsFilled
)
o << "<span class='dynamicEdit' id='statusListDropdown'>"
o << "<select data-issue='#{issue_id}'>"
o << "<option disabled='disabled' selected> </option>"
statuses.each do |s|
if (s != issue.status)
o << "<option value='#{s.id}'>#{s.name}</option>"
else
o << "<option value='#{s.id}' selected>#{s.name}</option>"
end
end
o << "</select>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# Users dropdown
assignables = project.assignable_users
if (
!assignables.empty? &&
!readOnlyAttributes.include?('assigned_to_id')
)
o << "<span class='dynamicEdit' id='usersListDropdown'>"
o << "<select data-issue='#{issue_id}'>"
o << "<option disabled='disabled' selected> </option>"
assignables.each do |u|
if (u != issue.assigned_to)
o << "<option value='#{u.id}'>#{u.name}</option>"
else
o << "<option value='#{u.id}' selected>#{u.name}</option>"
end
end
o << "</select>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# Priorities dropdown
priorities = IssuePriority.all
if !priorities.empty? && !(readOnlyAttributes.include? 'priority_id')
o << "<span class='dynamicEdit' id='prioritiesListDropdown'>"
o << "<select data-issue='#{issue_id}'>"
o << "<option disabled='disabled' selected> </option>"
priorities.each do |p|
if (p != issue.priority)
o << "<option value='#{p.id}'>#{p.name}</option>"
else
o << "<option value='#{p.id}' selected>#{p.name}</option>"
end
end
o << "</select>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# Categories dropdown
categories = project.issue_categories
if !categories.empty? && !(readOnlyAttributes.include? 'category_id')
o << "<span class='dynamicEdit' id='categoriesListDropdown'>"
o << "<select data-issue='#{issue_id}'>"
o << "<option value='' selected> </option>"
categories.each do |c|
if (c != issue.category)
o << "<option value='#{c.id}'>#{c.name}</option>"
else
o << "<option value='#{c.id}' selected>#{c.name}</option>"
end
end
o << "</select>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# %done dropdown
if ! readOnlyAttributes.include?('done_ratio')
percent = 0
o << "<span class='dynamicEdit' id='doneRatioListDropdown'>"
o << "<select data-issue='#{issue_id}'>"
o << "<option disabled='disabled' selected> </option>"
loop do
if percent == issue.done_ratio
o << "<option value='#{percent}' selected>#{percent}%</option>"
else
o << "<option value='#{percent}'>#{percent}%</option>"
end
percent += 10
break if percent == 110
end
o << "</select>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# Estimated_time dropdown
if ! readOnlyAttributes.include?('estimated_hours')
o << "<span class='dynamicEdit' id='EstimatedTimeInput'>"
o << " <input type='text' value='#{issue.estimated_hours}' size='6'/>"
o << "<a href='#' class='btn btn-primary validate' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
end
# Start date
if ! readOnlyAttributes.include?('start_date')
o << "<span class='dynamicEdit' id='StartDateInput'>"
o << " <input size=\"10\" value=\"#{issue.start_date}\" type=\"date\" max=\"#{issue.due_date}\">"
o << " <a href='#' class='btn btn-primary validate' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
o << "<script>"
o << "//<![CDATA[\n"
o << " if(typeof datepickerOptions !== 'undefined'){\n"
o << " $(function() { $('#StartDateInput input').addClass('date').datepickerFallback(datepickerOptions); });\n"
o << " }\n"
o << "//]]>\n"
o << "</script>"
end
# Due date
if ! readOnlyAttributes.include?('due_date')
o << "<span class='dynamicEdit' id='DueDateInput'>"
o << " <input size=\"10\" value=\"#{issue.due_date}\" type=\"date\" min=\"#{issue.start_date}\">"
o << " <a href='#' class='btn btn-primary validate' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "</span>"
o << "<script>"
o << "//<![CDATA[\n"
o << " if(typeof datepickerOptions !== 'undefined'){\n"
o << " $(function() { $('#DueDateInput input').addClass('date').datepickerFallback(datepickerOptions); });\n"
o << " }\n"
o << "//]]>\n"
o << "</script>"
end
# Title
# Make quotings in subject! (PP)
clonesubject = issue.subject.gsub('"','&quot;')
o << "<span class='dynamicEdit' id='TitleInput'>"
o << " <input size=\"50\" value=\"#{clonesubject}\" type=\"text\">"
o << " <a href='#' class='btn btn-primary validate' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a>"
o << "<script>"
o << "//<![CDATA[\n"
o << " function resizeTitleInput() {\n"
o << " $('#TitleInput input')[0].size = Math.max(50, Math.trunc(window.innerWidth / 11));\n"
o << " }\n"
o << " resizeTitleInput();\n"
o << " window.addEventListener('resize', resizeTitleInput);\n"
o << "//]]>\n"
o << "</script>"
o << "</span>"
# Description
o << "<span class='dynamicEdit' id='DescriptionInput'>"
o << " <textarea name='description' id='description_textarea' cols='60' rows='10' style='width:calc(100\% - 10px)'>#{issue.description}</textarea>"
o << " <div style='display:block; text-align:right; margin-top:10px;'><a href='#' class='btn btn-primary validate' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
o << " <a href='#' class='btn btn-primary close' aria-label='" + l(:ide_txt_cancel_btn) + "'><i class='fa fa-times fa-fw' aria-hidden='true'></i></a></div>"
o << "</span>"
# JS Part at the end of the edit block
o << "<script>"
o << " var CF_VALUE_JSON = " + issue.editable_custom_field_values(User.current).to_json + ";\n"
o << " var _ISSUE_ID = \"#{issue_id}\";\n"
o << " var _USER_API_KEY = \"#{User.current.api_key}\";\n"
o << " var _BASE_REDMINE_PATH = \"#{Redmine::Utils.relative_url_root}\";\n"
# Translations text
o << " var _TXT_VALIDATION_BTN = \"" + l(:ide_txt_validation_btn) + "\";\n"
o << " var _TXT_CANCEL_BTN = \"" + l(:ide_txt_cancel_btn) + "\";\n"
o << " var _TXT_ERROR_POSITIVE_NUMBER = \"" + l(:ide_txt_error_positive_number) + "\";\n"
o << " var _TXT_ERROR_START_DATE = \"" + l(:ide_txt_error_start_date) + "\";\n"
o << " var _TXT_ERROR_DUE_DATE = \"" + l(:ide_txt_error_due_date) + "\";\n"
o << " var _TXT_ERROR_AJAX_CALL = \"" + l(:ide_txt_error_ajax_call) + "\";\n"
o << " var _TXT_REQUIRED_FIELD = \"" + l(:ide_txt_required_field) + "\";\n"
o << "</script>\n"
o << "<div style='display:none' id='required_field_array'>#{requiredAttributes.to_json}</div>\n"
# closing the display none div parent
o << "</div>"
end
end
return o
def view_issues_show_details_bottom(context)
if User.current.allowed_to?(:edit_issues, context[:project])
content = "<script>\n"
content << " var _TXT_CONFLICT_TITLE = \"" + l(:ide_txt_notice_conflict_title) + "\";\n"
content << " var _TXT_CONFLICT_TXT = \"" + l(:ide_txt_notice_conflict_text) + "\";\n"
content << "</script>\n"
return content.html_safe
end
end
end