diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index d9cfa460d..5bb12fa9f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -882,8 +882,14 @@ EOF $aArgs).''; } $aFieldsMap[$sAttCode] = $sInputId; + + // Attribute description + $sDescription = $oAttDef->GetDescription(); + $sDescriptionForHTMLTag = utils::HtmlEntities($sDescription); + $sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag)) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.'"'; + $val = array( - 'label' => ''.$oAttDef->GetLabel().'', + 'label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, @@ -892,8 +898,13 @@ EOF } else { + // Attribute description + $sDescription = $oAttDef->GetDescription(); + $sDescriptionForHTMLTag = utils::HtmlEntities($sDescription); + $sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag)) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.'"'; + $val = array( - 'label' => ''.$oAttDef->GetLabel().'', + 'label' => ''.$oAttDef->GetLabel().'', 'value' => "".$this->GetAsHTML($sAttCode)."", 'comments' => $sComments, 'infos' => $sInfos, @@ -3240,9 +3251,14 @@ EOF $sDisplayValue = $this->GetAsHTML($sAttCode); } } + + // Attribute description + $sDescription = $oAttDef->GetDescription(); + $sDescriptionForHTMLTag = utils::HtmlEntities($sDescription); + $sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag)) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.'"'; + $retVal = array( - 'label' => ''.MetaModel::GetLabel($sClass, $sAttCode).'', + 'label' => ''.MetaModel::GetLabel($sClass, $sAttCode).'', 'value' => $sDisplayValue, ); } diff --git a/css/backoffice/components/_field.scss b/css/backoffice/components/_field.scss index 7e59ce324..ad5e8b73e 100644 --- a/css/backoffice/components/_field.scss +++ b/css/backoffice/components/_field.scss @@ -6,6 +6,10 @@ $ibo-field--sibling-spacing: 16px !default; $ibo-field--value--color: $ibo-color-grey-700 !default; +$ibo-field--label--description--content: "?" !default; +$ibo-field--label--description--padding-left: 2px !default; +$ibo-field--label--description--color: $ibo-color-grey-700 !default; + .ibo-field { @extend %ibo-font-ral-nor-150; @@ -37,6 +41,18 @@ $ibo-field--value--color: $ibo-color-grey-700 !default; max-width: 145px; width: 30%; padding-right: 10px; + + > .ibo-has-description { + &::after { + content: $ibo-field--label--description--content; + padding-left: $ibo-field--label--description--padding-left; + vertical-align: top; + + cursor: pointer; + color: $ibo-field--label--description--color; + @extend %ibo-font-ral-bol-100; + } + } } .ibo-field--value { diff --git a/js/pages/backoffice.js b/js/pages/backoffice.js index e4c58dc34..63bf9418a 100644 --- a/js/pages/backoffice.js +++ b/js/pages/backoffice.js @@ -68,14 +68,46 @@ const CombodoBackofficeToolbox = { // Instanciate tooltips (abstraction layer between iTop markup and tooltip plugin to ease its replacement in the future) /** * Instanciate a tooltip on oElem from its data attributes + * + * Note: Content SHOULD be HTML entity encoded to avoid markup breaks (eg. when using a double quote in a sentence) + * * @param oElem * @constructor */ InitTooltipFromMarkup: function(oElem) { - const oOptions = {}; + const oOptions = { + allowHTML: true, // Always true so line breaks can work. Don't worry content will be sanitized. + }; + + // Content must be reworked before getting into the tooltip + // - Should we enable HTML content or keep text as is + const bEnableHTML = oElem.attr('data-tooltip-html-enabled') === 'true'; + + // - Content should be sanitized unless the developer says otherwise + // Note: Condition is inversed on purpose. When the developer is instanciating a tooltip, + // we want him/her to explicitly declare that he/she wants the sanitizer to be skipped. + // Whereas in this code, it's easier to follow the logic with the variable oriented this way. + const bSanitizeContent = oElem.attr('data-tooltip-sanitizer-skipped') !== 'true'; + + // - Sanitize content and make sure line breaks are kept + const oTmpContentElem = $('
').html(oElem.attr('data-tooltip-content')); + let sContent = ''; + if(bEnableHTML) + { + sContent = oTmpContentElem.html(); + if(bSanitizeContent) + { + sContent = sContent.replace(/'); + } + oOptions['content'] = sContent; - oOptions['content'] = oElem.attr('data-tooltip-content'); oOptions['placement'] = oElem.attr('data-tooltip-placement') ?? 'top'; oOptions['trigger'] = oElem.attr('data-tooltip-trigger') ?? 'mouseenter focus';