mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-19 07:12:26 +02:00
Merge remote-tracking branch 'origin/develop' into feature/4157
# Conflicts: # application/ui.linkswidget.class.inc.php # core/dbobject.class.php
This commit is contained in:
@@ -24,8 +24,15 @@ if ((CKEDITOR !== undefined) && (CKEDITOR.plugins.registered['disabler'] === und
|
||||
|
||||
// Rewrite the CKEditor Mentions plugin regexp to make it suitable for all Unicode alphabets.
|
||||
if (CKEDITOR !== undefined && CKEDITOR.plugins.registered['mentions']) {
|
||||
// from https://github.com/ckeditor/ckeditor4/blob/a3786007fb979d7d7bff3d10c34a2d422935baed/plugins/mentions/plugin.js#L147
|
||||
// From https://github.com/ckeditor/ckeditor4/blob/a3786007fb979d7d7bff3d10c34a2d422935baed/plugins/mentions/plugin.js#L147
|
||||
function createPattern(marker, minChars) {
|
||||
// Escape marker if it's a regex token
|
||||
// https://github.com/tc39/proposal-regex-escaping/blob/main/EscapedChars.md#syntaxcharacter-proposal
|
||||
const regexTokens = ['^', '$', '\\', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '|'];
|
||||
if (regexTokens.indexOf(marker) >= 0) {
|
||||
marker = '\\' + marker;
|
||||
}
|
||||
|
||||
let pattern = marker + '[\\p{L}\\p{N}_-]';
|
||||
if ( minChars ) {
|
||||
pattern += '{' + minChars + ',}';
|
||||
|
||||
@@ -35,8 +35,7 @@ $(function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this.element
|
||||
.append('<div class="last-error"></div>')
|
||||
this.element.append('<div class="last-error"></div>')
|
||||
.addClass('console_form_handler');
|
||||
|
||||
this.options.oWizardHelper = window[this.options.wizard_helper_var_name];
|
||||
@@ -48,13 +47,13 @@ $(function()
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element
|
||||
.removeClass('console_form_handler');
|
||||
this.element.removeClass('console_form_handler');
|
||||
this._super();
|
||||
},
|
||||
_onUpdateFields: function(event, data)
|
||||
{
|
||||
var me = this;
|
||||
me._updatePreviousValues();
|
||||
var sFormPath = data.form_path;
|
||||
var sUpdateUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php';
|
||||
|
||||
@@ -64,7 +63,6 @@ $(function()
|
||||
{
|
||||
operation: 'custom_fields_update',
|
||||
attcode: this.options.custom_field_attcode,
|
||||
//current_values: this.getCurrentValues(),
|
||||
requested_fields: data.requested_fields,
|
||||
form_path: sFormPath,
|
||||
json_obj: this.options.oWizardHelper.UpdateWizardToJSON()
|
||||
@@ -85,6 +83,7 @@ $(function()
|
||||
me.element.find('.last-error').text(data.error);
|
||||
}
|
||||
me._onUpdateAlways(data, sFormPath);
|
||||
me.element.find('[data-field-id="previous_values"]').find('input[type="hidden"]').val('{}');
|
||||
});
|
||||
},
|
||||
// On initialization or update
|
||||
|
||||
@@ -78,4 +78,30 @@ function getMultipleSelectionParams(listId)
|
||||
});
|
||||
|
||||
return oRes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column JSON declaration for row actions.
|
||||
* Could be part of column or columnDefs declaration of datatable.js.
|
||||
*
|
||||
* @param sTableId
|
||||
* @param iColumnTargetIndex
|
||||
* @returns {*}
|
||||
* @since 3.1.0
|
||||
*/
|
||||
function getRowActionsColumnDefinition(sTableId, iColumnTargetIndex = -1)
|
||||
{
|
||||
let aColumn = {
|
||||
type: "html",
|
||||
orderable: false,
|
||||
render: function ( data, type, row, meta ) {
|
||||
return $(`#${sTableId}_actions_buttons_template`).html();
|
||||
}
|
||||
};
|
||||
|
||||
if (iColumnTargetIndex !== -1) {
|
||||
aColumn['targets'] = iColumnTargetIndex;
|
||||
}
|
||||
|
||||
return aColumn;
|
||||
}
|
||||
|
||||
@@ -92,12 +92,27 @@ $(function()
|
||||
{
|
||||
return this.options.field_set.triggerHandler('get_current_values');
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @since 3.0.2 3.1.0
|
||||
*/
|
||||
_updatePreviousValues: function()
|
||||
{
|
||||
let me = this;
|
||||
if(this.element.find('[data-attribute-previous-value]').length > 0) {
|
||||
let aPreviousValues = {};
|
||||
$(this.element.find('[data-attribute-previous-value]')).each(function (iIdx, oElem) {
|
||||
aPreviousValues[$(oElem).data('field-id')] = $(oElem).data('attribute-previous-value');
|
||||
});
|
||||
me.element.find('[data-field-id="previous_values"]').find('input[type="hidden"]').val(JSON.stringify(aPreviousValues));
|
||||
}
|
||||
},
|
||||
// Events callback
|
||||
// - Update fields depending on the update ones
|
||||
_onUpdateFields: function(oEvent, oData)
|
||||
{
|
||||
var me = this;
|
||||
me._updatePreviousValues();
|
||||
var sFormPath = oData.form_path;
|
||||
|
||||
// Data checks
|
||||
|
||||
@@ -156,7 +156,7 @@ $(function()
|
||||
});
|
||||
me._updateExtraTabsList();
|
||||
}, {
|
||||
root: $('.ibo-tab-container--tabs-list')[0],
|
||||
root: this.element.find(this.js_selectors.tabs_list)[0],
|
||||
threshold: [0.9] // N°4783 Should be completely visible, but lowering the threshold prevents a bug in the JS Observer API when the window is zoomed in/out, in which case all items respond as being hidden even when they are not.
|
||||
});
|
||||
this.element.find(this.js_selectors.tab_header).each(function(){
|
||||
@@ -262,6 +262,16 @@ $(function()
|
||||
// Prevent anchor default behaviour
|
||||
oEvent.preventDefault();
|
||||
|
||||
// Compute list position
|
||||
// Note: Arbitrary +6px for the position as we don't want it to be exactly against the toggler
|
||||
let fTopOffset = this.element.find(this.js_selectors.extra_tabs_list_toggler).offset().top + this.element.find(this.js_selectors.extra_tabs_list_toggler).outerHeight() + 6;
|
||||
// We need to compute position from the right side of the screen because at this time the list isn't visible and we can't know its width, so we can't position it regarding the left side of the screen
|
||||
// Note: We use window.innerWidth instead of outerWidth as we need the width of the actual viewport, not the OS browser window
|
||||
let fRightOffset = window.innerWidth - this.element.find(this.js_selectors.extra_tabs_list_toggler).offset().left - this.element.find(this.js_selectors.extra_tabs_list_toggler).outerWidth();
|
||||
this.element.find(this.js_selectors.extra_tabs_list)
|
||||
.css('top', fTopOffset + 'px')
|
||||
.css('right', fRightOffset + 'px');
|
||||
|
||||
// TODO 3.0.0: Should/could we use a popover menu instead here?
|
||||
this.element.find(this.js_selectors.extra_tabs_list).toggleClass(this.css_classes.is_hidden);
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* @since 3.0.0 Add iMaxAddedId parameter
|
||||
*/
|
||||
function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper, sExtKeyToRemote, bDoSearch, iMaxAddedId = 0) {
|
||||
function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper, sExtKeyToRemote, bDoSearch, iMaxAddedId = 0, aRemoved = []) {
|
||||
this.id = id;
|
||||
this.iInputId = iInputId;
|
||||
this.sClass = sClass;
|
||||
@@ -30,7 +30,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
||||
this.sExtKeyToRemote = sExtKeyToRemote;
|
||||
this.iMaxAddedId = iMaxAddedId;
|
||||
this.aAdded = [];
|
||||
this.aRemoved = [];
|
||||
this.aRemoved = aRemoved;
|
||||
this.aModified = {};
|
||||
this.bDoSearch = bDoSearch; // false if the search is not launched
|
||||
let me = this;
|
||||
|
||||
@@ -428,7 +428,6 @@ $(function()
|
||||
bSearchMode: 'true',
|
||||
sOutputFormat: 'json',
|
||||
operation: 'ac_extkey',
|
||||
sAutocompleteOperation: 'equals_start_with'
|
||||
}
|
||||
)
|
||||
.done(function(oResponse, sStatus, oXHR){
|
||||
@@ -441,28 +440,7 @@ $(function()
|
||||
return;
|
||||
}
|
||||
|
||||
oACXHR = $.post(
|
||||
AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'),
|
||||
{
|
||||
sTargetClass: me.options.field.target_class,
|
||||
sFilter: 'SELECT ' + me.options.field.target_class,
|
||||
q: sQuery,
|
||||
bSearchMode: 'true',
|
||||
sOutputFormat: 'json',
|
||||
operation: 'ac_extkey',
|
||||
sAutocompleteOperation: 'contains'
|
||||
}
|
||||
)
|
||||
.done(function(oResponseContains, sStatus, oXHR){
|
||||
//filter duplicates
|
||||
$.each(oResponse, function(index, value) {
|
||||
delete oResponseContains[index];
|
||||
});
|
||||
|
||||
me._onACSearchContainsSuccess(oResponseContains);
|
||||
|
||||
|
||||
});
|
||||
me._onACSearchContainsSuccess(oResponse);
|
||||
})
|
||||
.fail(function(oResponse, sStatus, oXHR){ me._onACSearchFail(oResponse, sStatus); })
|
||||
.always(function(oResponse, sStatus, oXHR){
|
||||
@@ -701,44 +679,16 @@ $(function()
|
||||
}
|
||||
}
|
||||
this._setACWaitTempHint();
|
||||
},
|
||||
|
||||
// Autocomplete CONTAINS callbacks
|
||||
_onACSearchContainsSuccess: function(oResponse)
|
||||
{
|
||||
if(typeof oResponse !== 'object')
|
||||
{
|
||||
this._emptyACTempHint();
|
||||
return false;
|
||||
}
|
||||
|
||||
var oDynamicListElem = this.element.find('.sfc_opc_mc_items_dynamic');
|
||||
if(Object.keys(oResponse).length > 0)
|
||||
{
|
||||
// Note: Response is indexed by labels from server so the JSON is always ordered on decoding.
|
||||
for(var skey in oResponse)
|
||||
{
|
||||
var sValue = oResponse[skey].value;
|
||||
var sLabel = oResponse[skey].label;
|
||||
|
||||
// Note: We don't use the _isSelectedValue() method here as it only returns "applied" values; at this moment will could have a checked value that is not among selected (me.options.values) yet. The result would be an hidden item from the AC results.
|
||||
var bSelected = (this.element.find(this._getSelectedValuesWrapperSelector() + ' .sfc_opc_mc_item[data-value-code="' + sValue + '"]').length > 0);
|
||||
var bInitChecked = bSelected;
|
||||
var bInitHidden = bSelected;
|
||||
var oValueElem = this._makeListItemElement(sLabel, sValue, bInitChecked, bInitHidden,oResponse[skey].obsolescence_flag,oResponse[skey].additional_field);
|
||||
oValueElem.appendTo(oDynamicListElem);
|
||||
}
|
||||
}
|
||||
|
||||
if (oDynamicListElem.find('.sfc_opc_mc_item').length == 0)
|
||||
{
|
||||
this._setACNoResultHint();
|
||||
}
|
||||
else
|
||||
{
|
||||
this._emptyACTempHint();
|
||||
if (oDynamicListElem.find('.sfc_opc_mc_item').length == 0)
|
||||
{
|
||||
this._setACNoResultHint();
|
||||
}
|
||||
},
|
||||
else
|
||||
{
|
||||
this._emptyACTempHint();
|
||||
}
|
||||
},
|
||||
_onACSearchFail: function(oResponse, sStatus)
|
||||
{
|
||||
if(sStatus !== 'abort')
|
||||
|
||||
16
js/utils.js
16
js/utils.js
@@ -389,11 +389,13 @@ function ExportListDlg(sOQL, sDataTableId, sFormat, sDlgTitle) {
|
||||
var oColumns = $('#'+sDataTableName).DataTable().ajax.params()['columns'];
|
||||
for (var j in oColumns) {
|
||||
if (oColumns[j]['data']) {
|
||||
var sCode = oColumns[j]['data'].split("/");
|
||||
if (sCode[1] == '_key_') {
|
||||
sCode[1] = 'id';
|
||||
if (oColumns[j]['data']!='id') {
|
||||
var sCode = oColumns[j]['data'].split("/");
|
||||
if (sCode[1] == '_key_') {
|
||||
sCode[1] = 'id';
|
||||
}
|
||||
aFields.push(sCode[0]+'.'+sCode[1]);
|
||||
}
|
||||
aFields.push(sCode[0]+'.'+sCode[1]);
|
||||
} else {
|
||||
for (var k in oColumns[j]) {
|
||||
if (oColumns[j][k].checked) {
|
||||
@@ -807,8 +809,10 @@ const CombodoTooltip = {
|
||||
oOptions['content'] = sContent;
|
||||
|
||||
// Interaction (selection, click, ...) have to be enabled manually
|
||||
// Important: When set to true, if "data-tooltip-append-to" is not specified, tooltip will be append to the parent element instead of the body
|
||||
const bInteractive = oElem.attr('data-tooltip-interaction-enabled') === 'true';
|
||||
// Important: When set to true, if "data-tooltip-append-to" is not specified, tooltip will be appended to the parent element instead of the body
|
||||
// Note: Defaults to true if it contains hyperlink
|
||||
let bDefaultInteractive = (bEnableHTML && sContent.indexOf("<a ") > -1)
|
||||
const bInteractive = oElem.attr('data-tooltip-interaction-enabled') !== undefined ? oElem.attr('data-tooltip-interaction-enabled') === 'true' : bDefaultInteractive;
|
||||
oOptions['interactive'] = bInteractive;
|
||||
|
||||
// Element to append the tooltip to
|
||||
|
||||
@@ -242,8 +242,8 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) {
|
||||
|
||||
this.ResetQuery();
|
||||
this.UpdateWizard();
|
||||
var fieldForm=null;
|
||||
while (index < aFieldNames.length)
|
||||
var fieldForm = null;
|
||||
while (index < aFieldNames.length )
|
||||
{
|
||||
sAttCode = aFieldNames[index];
|
||||
sFieldId = this.GetFieldId(sAttCode);
|
||||
@@ -255,13 +255,13 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) {
|
||||
message: '',
|
||||
overlayCSS: {backgroundColor: '#f1f1f1', opacity: 0.3}
|
||||
});
|
||||
fieldForm=$('#field_' + sFieldId).closest('form');
|
||||
fieldForm = $('#field_' + sFieldId).closest('form');
|
||||
this.RequestAllowedValues(sAttCode);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if($('.blockUI').length > 0) {
|
||||
if ((fieldForm !== null) && ($('.blockUI').length > 0)) {
|
||||
fieldForm.find('button[type=submit]:not(:disabled)').prop("disabled", true).addClass('disabledDuringFieldLoading');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user