diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 29ab8933cb..05cd9950b1 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -90,6 +90,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_stylesheet("../css/jquery.multiselect.css"); $this->add_linked_stylesheet("../css/magnific-popup.css"); $this->add_linked_stylesheet("../css/c3.min.css"); + $this->add_linked_stylesheet("../node_modules/tippy.js/dist/tippy.css"); + $this->add_linked_stylesheet("../node_modules/tippy.js/animations/shift-away-subtle.css"); $this->add_linked_stylesheet("../css/font-awesome/css/all.min.css"); $this->add_linked_stylesheet("../css/font-combodo/font-combodo.css"); $this->add_linked_stylesheet("../js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css"); @@ -107,7 +109,10 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_script("../js/ckeditor/ckeditor.js"); $this->add_linked_script("../js/ckeditor/adapters/jquery.js"); $this->add_linked_script("../js/ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js"); + /* @deprecated qTip will be removed in 2.9.0, use Tippy.js instead */ $this->add_linked_script("../js/jquery.qtip-1.0.min.js"); + $this->add_linked_script("../node_modules/@popperjs/core/dist/umd/popper.js"); + $this->add_linked_script("../node_modules/tippy.js/dist/tippy-bundle.umd.js"); $this->add_linked_script('../js/property_field.js'); $this->add_linked_script('../js/icon_select.js'); $this->add_linked_script('../js/raphael-min.js'); @@ -119,6 +124,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_script('../js/jquery.magnific-popup.min.js'); $this->add_linked_script('../js/moment-with-locales.min.js'); $this->add_linked_script('../js/showdown.min.js'); + $this->add_linked_script('../js/pages/backoffice.js'); $this->add_linked_script('../js/newsroom_menu.js'); $this->add_dict_entry('UI:FillAllMandatoryFields'); @@ -133,60 +139,34 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage if (!$this->IsPrintableVersion()) { $this->PrepareLayout(); - $this->add_script( - <<Get('demo_mode')) { - // Leave the pane opened + // Leave the menu collapsed } else { if (utils::ReadParam('force_menu_pane', null) === 0) { - $bLeftPaneOpen = false; + $bIsExpanded = false; } - elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed') + elseif (appUserPreferences::GetPref('menu_pane', 'closed') === 'opened') { - $bLeftPaneOpen = false; + $bIsExpanded = true; } } - return $bLeftPaneOpen; + return $bIsExpanded; } /** @@ -195,22 +175,22 @@ EOF protected function PrepareLayout() { // TODO: Move this to the menu renderer - if (MetaModel::GetConfig()->Get('demo_mode')) - { - // No pin button - $sConfigureWestPane = ''; - } - else - { - $sConfigureWestPane = - <<IsMenuPaneVisible() ? '' : 'initClosed: true,'; +// if (MetaModel::GetConfig()->Get('demo_mode')) +// { +// // No pin button +// $sConfigureWestPane = ''; +// } +// else +// { +// $sConfigureWestPane = +// <<IsMenuPaneVisible() ? '' : 'initClosed: true,'; $sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage')); $sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle')); @@ -815,6 +795,33 @@ JS return $sHtml; } + /** + * Return the language for the page metadata based on the current user + * + * @return string + * @since 2.8.0 + */ + protected function GetLanguageForMetadata() + { + $sUserLang = UserRights::GetUserLanguage(); + + return strtolower(substr($sUserLang, 0 ,2)); + } + + /** + * Return the absolute URL for the favicon + * + * @return string + * @throws \Exception + * @since 2.8.0 + */ + protected function GetFaviconAbsoluteUrl() + { + // TODO: Make it a property so it can be changed programmatically + // TODO: How to set both dark/light mode favicons + return utils::GetAbsoluteUrlAppRoot().'images/favicon.ico'; + } + /** * Return the complete revision number of the application * @@ -856,6 +863,7 @@ JS 'sAppFullIconUrl' => Branding::GetFullMainLogoAbsoluteUrl(), 'sAppIconLink' => MetaModel::GetConfig()->Get('app_icon_url'), 'aMenuGroups' => ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash()), + 'bIsExpanded' => $this->IsMenuPaneVisible(), ]; } @@ -1075,11 +1083,12 @@ EOF // Prepare page metadata $sAbsoluteUrlAppRoot = addslashes($this->m_sRootUrl); - // TODO: Make it a property so it can be changed programmatically - // TODO: How to set both dark/light mode favicons - $sFaviconUrl = $sAbsoluteUrlAppRoot.'images/favicon.ico'; - // TODO: Get this for the current user language - $sMetadataLanguage = 'en'; + $sFaviconUrl = $this->GetFaviconAbsoluteUrl(); + $sMetadataLanguage = $this->GetLanguageForMetadata(); + + // Prepare internal parts (js files, css files, js snippets, css snippets, ...) + // - Generate necessary dict. files + $this->output_dict_entries(); // Base structure of data to pass to the TWIG template $aData['aPage'] = [ @@ -1468,7 +1477,7 @@ EOF; $sHtml .= '
'; $sHtml .= self::FilterXSS($sApplicationBanner); - $GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : ''; +// $GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : ''; $sHtml .= ' '; $sHtml .= ' '; diff --git a/css/backoffice/components/_breadcrumbs.scss b/css/backoffice/components/_breadcrumbs.scss index cd210f7794..19ab8740d3 100644 --- a/css/backoffice/components/_breadcrumbs.scss +++ b/css/backoffice/components/_breadcrumbs.scss @@ -22,6 +22,8 @@ $ibo-breadcrumbs--item-icon--margin-x: 8px !default; $ibo-breadcrumbs--item-icon--max-width: 16px !default; $ibo-breadcrumbs--item-icon--text-color: $ibo-color-grey-600 !default; +$ibo-breadcrumbs--item-label--max-width: 100px !default; + $ibo-breadcrumbs--item-separator--margin-x: 12px !default; $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default; @@ -72,3 +74,8 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default; filter: grayscale(100%); } } +.ibo-breadcrumbs--item-label{ + display: inline; /* Otherwise the "truncate" doesn't work with th default display: flex */ + max-width: $ibo-breadcrumbs--item-label--max-width; + @extend %ibo-text-truncated-with-ellipsis; +} diff --git a/css/backoffice/layout/_navigation-menu.scss b/css/backoffice/layout/_navigation-menu.scss index 90ce4972fe..1eb0f7d901 100644 --- a/css/backoffice/layout/_navigation-menu.scss +++ b/css/backoffice/layout/_navigation-menu.scss @@ -72,12 +72,14 @@ $ibo-navigation-menu--menu-group-icon--text-color--is-active: $ibo-color-primary $ibo-navigation-menu--menu-group-title--margin-left: $ibo-navigation-menu--middle-part--padding-x !default; $ibo-navigation-menu--menu-group-title--text-color--is-active: $ibo-color-blue-grey-800 !default; -$ibo-navigation-menu--drawer--background-color: $ibo-color-grey-100 !default; $ibo-navigation-menu--drawer--width: 312px !default; $ibo-navigation-menu--drawer--padding-x: 20px !default; $ibo-navigation-menu--drawer--padding-y: 32px !default; +$ibo-navigation-menu--drawer--background-color: $ibo-color-grey-100 !default; +$ibo-navigation-menu--drawer--border-right: 1px solid $ibo-color-grey-300 !default; -$ibo-navigation-menu--menu-filter--margin-bottom: 55px !default; +$ibo-navigation-menu--menu-filter--width: $ibo-navigation-menu--drawer--width - $ibo-navigation-menu--drawer--padding-x * 2 !default; +$ibo-navigation-menu--menu-filter--margin-bottom: 50px !default; $ibo-navigation-menu--menu-filter-input--padding-right: 64px !default; $ibo-navigation-menu--menu-filter-hotkey--border: 1px solid $ibo-color-grey-400 !default; @@ -91,14 +93,18 @@ $ibo-navigation-menu--menu-filter-input--background-color: $ibo-color-white-100 $ibo-navigation-menu--menu-filter-input--border: 1px solid $ibo-color-grey-300 !default; $ibo-navigation-menu--menu-filter-input--border-radius: $ibo-border-radius-300 !default; +$ibo-navigation-menu--menu-groups--margin-top: $ibo-navigation-menu--drawer--padding-y + $ibo-navigation-menu--menu-filter--margin-bottom !default; + +$ibo-navigation-menu--menu-nodes--margin-bottom--on-filtered: 48px !default; + $ibo-navigation-menu--menu-nodes-title--margin-top: 0 !default; $ibo-navigation-menu--menu-nodes-title--margin-bottom: 32px !default; +$ibo-navigation-menu--menu-nodes-title--margin-bottom--on-filtered: 8px !default; $ibo-navigation-menu--menu-node--padding-x: 10px !default; $ibo-navigation-menu--menu-node--padding-y: 6px !default; $ibo-navigation-menu--menu-node--margin-x: -1 * $ibo-navigation-menu--menu-node--padding-x !default; $ibo-navigation-menu--menu-node--margin-y: 0 !default; -$ibo-navigation-menu--menu-node--margin-top: 8px !default; $ibo-navigation-menu--menu-node--text-color: $ibo-color-grey-700 !default; $ibo-navigation-menu--menu-node--hyperlink-color: inherit !default; $ibo-navigation-menu--menu-node--background-color: $ibo-color-grey-200 !default; @@ -155,6 +161,15 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default; right: calc(-1 * #{$ibo-navigation-menu--drawer--width}); } } + &.ibo-navigation-menu--is-filtered{ + .ibo-navigation-menu--menu-nodes{ + margin-bottom: $ibo-navigation-menu--menu-nodes--margin-bottom--on-filtered; + + .ibo-navigation-menu--menu-nodes-title{ + margin-bottom: $ibo-navigation-menu--menu-nodes-title--margin-bottom--on-filtered; + } + } + } } .ibo-navigation-menu--body, @@ -340,12 +355,13 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default; overflow-y: auto; background-color: $ibo-navigation-menu--drawer--background-color; + border-right: $ibo-navigation-menu--drawer--border-right; transition: right 0.2s ease-in-out; } /* - Menu filter */ .ibo-navigation-menu--menu-filter{ - position: relative; - margin-bottom: $ibo-navigation-menu--menu-filter--margin-bottom; + position: fixed; + width: $ibo-navigation-menu--menu-filter--width; } .ibo-navigation-menu--menu-filter-input{ /* TODO: Refactor this into the standard field input */ @@ -380,6 +396,10 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default; padding: 2px 4px; @extend %ibo-font-ral-nor-50; } +/* - Menu groups */ +.ibo-navigation-menu--menu-groups{ + margin-top: $ibo-navigation-menu--menu-groups--margin-top; +} /* - Menu nodes */ .ibo-navigation-menu--menu-nodes{ display: none; @@ -405,7 +425,6 @@ $ibo-navigation-menu--menu-node--border-radius: $ibo-border-radius-500 !default; } } ul{ - margin-top: $ibo-navigation-menu--menu-node--margin-top; padding-left: $ibo-navigation-menu--drawer--padding-x; } } diff --git a/css/backoffice/layout/_top-bar.scss b/css/backoffice/layout/_top-bar.scss index e359de6e10..06603cbeaa 100644 --- a/css/backoffice/layout/_top-bar.scss +++ b/css/backoffice/layout/_top-bar.scss @@ -37,8 +37,10 @@ $ibo-top-bar-background-color: $ibo-color-white-100 !default; height: var(--ibo-top-bar-height); padding: var(--ibo-top-bar-padding-y) var(--ibo-top-bar-padding-right) var(--ibo-top-bar-padding-y) var(--ibo-top-bar-padding-left); background-color: var(--ibo-top-bar-background-color); + @extend %ibo-elevation-100; .ibo-breadcrumbs{ flex-grow: 1; /* Occupy as much width as possible */ + overflow-x: hidden; /* Avoid glitches when too many items */ } } \ No newline at end of file diff --git a/css/backoffice/main.css b/css/backoffice/main.css index 2319bef0bd..7dc0561344 100644 --- a/css/backoffice/main.css +++ b/css/backoffice/main.css @@ -293,6 +293,9 @@ * * You should have received a copy of the GNU Affero General Public License */ +.ibo-top-bar { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.12); } + /*! * Copyright (C) 2013-2020 Combodo SARL * @@ -8148,12 +8151,13 @@ a { padding: 32px 20px; overflow-y: auto; background-color: #f8f9fa; + border-right: 1px solid #d5dde5; transition: right 0.2s ease-in-out; } /* - Menu filter */ .ibo-navigation-menu--menu-filter { position: relative; - margin-bottom: 55px; } + margin-bottom: 50px; } .ibo-navigation-menu--menu-filter-input { /* TODO: Refactor this into the standard field input */ @@ -8297,10 +8301,11 @@ body { background-color: var(--ibo-body-background-color); } #ibo-navigation-menu { - z-index: 1; } + z-index: 20; } #ibo-page-container { position: relative; + z-index: 10; height: 100%; overflow: auto; flex-grow: 1; } diff --git a/css/backoffice/pages/_base.scss b/css/backoffice/pages/_base.scss index 81ffac65eb..6c2587e26d 100644 --- a/css/backoffice/pages/_base.scss +++ b/css/backoffice/pages/_base.scss @@ -42,10 +42,11 @@ body{ @extend %ibo-font-ral-nor-100; } #ibo-navigation-menu{ - z-index: 1; + z-index: 20; } #ibo-page-container{ position: relative; + z-index: 10; height: 100%; overflow: auto; flex-grow: 1; diff --git a/js/components/breadcrumbs.js b/js/components/breadcrumbs.js index 64defba914..6dc1854c95 100644 --- a/js/components/breadcrumbs.js +++ b/js/components/breadcrumbs.js @@ -100,7 +100,7 @@ $(function() else { var sSanitizedUrl = StripArchiveArgument(oEntry['url']); - sBreadcrumbsItemHtml += ''+sIconSpec+''+oEntry['label']+''; + sBreadcrumbsItemHtml += ''+sIconSpec+''+oEntry['label']+''; } } this.element.append(sBreadcrumbsItemHtml); diff --git a/js/layouts/navigation-menu.js b/js/layouts/navigation-menu.js index 1ff31d8822..54551e6cc7 100644 --- a/js/layouts/navigation-menu.js +++ b/js/layouts/navigation-menu.js @@ -24,21 +24,32 @@ $(function() // default options options: { - init_expanded: false, active_menu_group: null, + filter_keyup_throttle: 200, // In milliseconds }, css_classes: { menu_expanded: 'ibo-navigation-menu--is-expanded', menu_active: 'ibo-navigation-menu--is-active', + menu_filtered: 'ibo-navigation-menu--is-filtered', menu_group_active: 'ibo-navigation-menu--menu-group--is-active', menu_nodes_active: 'ibo-navigation-menu--menu-nodes--is-active' }, + js_selectors: + { + menu_toggler: '[data-role="ibo-navigation-menu--toggler"]', + menu_group: '[data-role="ibo-navigation-menu--menu-group"]', + menu_drawer: '[data-role="ibo-navigation-menu--drawer"]', + menu_filter_input: '[data-role="ibo-navigation-menu--menu-filter-input"]', + menu_filter_clear: '[data-role="ibo-navigation-menu--menu-filter-clear"]', + }, + filter_throttle_timeout: null, // the constructor _create: function() { this.element.addClass('ibo-navigation-menu'); + this._bindEvents(); }, // events bound via _bind are removed automatically @@ -53,17 +64,31 @@ $(function() var oBodyElem = $('body'); // Click on collapse/expand toggler - this.element.find('[data-role="ibo-navigation-menu--toggler"]').on('click', function(oEvent){ + this.element.find(this.js_selectors.menu_toggler).on('click', function(oEvent){ me._onTogglerClick(oEvent); }); // Click on menu group - this.element.find('[data-role="ibo-navigation-menu--menu-group"]').on('click', function(oEvent){ + this.element.find(this.js_selectors.menu_group).on('click', function(oEvent){ me._onMenuGroupClick(oEvent, $(this)) }); // Mostly for outside clicks that should close elements oBodyElem.on('click', function(oEvent){ me._onBodyClick(oEvent); }); + // Mostly for hotkeys + oBodyElem.on('keyup', function(oEvent){ + me._onBodyKeyUp(oEvent); + }); + + // Menus filter + // - Input itself + this.element.find(this.js_selectors.menu_filter_input).on('keyup', function(oEvent){ + me._onFilterKeyUp(oEvent); + }); + // - Clear icon + this.element.find(this.js_selectors.menu_filter_clear).on('click', function(oEvent){ + me._onFilterClearClick(oEvent); + }) }, // Events callbacks @@ -72,8 +97,12 @@ $(function() // Avoid anchor glitch oEvent.preventDefault(); + // Toggle menu this.element.toggleClass(this.css_classes.menu_expanded); - // TODO: Save preference + + // Save state in user preferences + const sPrefValue = this.element.hasClass(this.css_classes.menu_expanded) ? 'opened' : 'closed'; + SetUserPreference('menu_pane', sPrefValue, true); }, _onMenuGroupClick: function(oEvent, oMenuGroupElem) { @@ -90,19 +119,86 @@ $(function() this._closeDrawer(); } }, + _onBodyKeyUp: function(oEvent) + { + // Note: We thought about extracting the oEvent.key in a variable to lower case it, but this would be done + // on every single key up in the application, which might not be what we want... (time consuming) + if((oEvent.altKey === true) && (oEvent.key === 'm' || oEvent.key === 'M')) + { + const me = this; + + if(this._getActiveMenuGroupId() === null) + { + const sFirstMenuGroupId = this.element.find(this.js_selectors.menu_group+':first').attr('data-menu-group-id'); + this._openDrawer(sFirstMenuGroupId); + } + + this._focusFilter(); + } + }, + + _onFilterKeyUp: function(oEvent) + { + const me = this; + const oInputElem = this.element.find(this.js_selectors.menu_filter_input); + const sValue = oInputElem.val(); + + if((sValue === '') || (oEvent.key === 'Escape')) + { + // Prevent other behaviors + oEvent.stopPropagation(); + + this._clearFiltering(); + } + else + { + // Reset throttle timeout on key stroke + clearTimeout(this.filter_throttle_timeout); + this.filter_throttle_timeout = setTimeout(function(){ + me._doFiltering(sValue); + }, this.options.filter_keyup_throttle); + } + }, + _onFilterClearClick: function(oEvent) + { + // Avoid anchor glitch + oEvent.preventDefault(); + + // Remove current filter value + this._clearFiltering(); + // Position focus in the input for better UX + this._focusFilter(); + }, // Methods _checkIfClickShouldCloseDrawer: function(oEvent) { if( - $(oEvent.target.closest('[data-role="ibo-navigation-menu--drawer"]')).length === 0 + $(oEvent.target.closest(this.js_selectors.menu_drawer)).length === 0 && $(oEvent.target.closest('[data-role="ibo-navigation-menu--menu-group"]')).length === 0 - && $(oEvent.target.closest('[data-role="ibo-navigation-menu--toggler"]')).length === 0 + && $(oEvent.target.closest(this.js_selectors.menu_toggler)).length === 0 ) { this._closeDrawer(); } }, + /** + * Return the ID of the active menu group, or null if none (typically when the drawer is closed) + * @returns {null|*} + * @private + */ + _getActiveMenuGroupId: function() + { + const oActiveMenuGroup = this.element.find('.'+this.css_classes.menu_group_active); + if(oActiveMenuGroup.length > 0) + { + return oActiveMenuGroup.attr('data-menu-group-id'); + } + else + { + return null; + } + }, /** * Clear the current active menu group but does NOT close the drawer * @private @@ -120,6 +216,8 @@ $(function() _openDrawer: function(sMenuGroupId) { this._clearActiveMenuGroup(); + // Note: This causes the filter to be cleared event when using the hotkey to reopen a previously filled filter + this._clearFiltering(); // Set new active group this.element.find('[data-role="ibo-navigation-menu--menu-group"][data-menu-group-id="'+sMenuGroupId+'"]').addClass(this.css_classes.menu_group_active); @@ -138,6 +236,79 @@ $(function() // Set menu as non active this.element.removeClass(this.css_classes.menu_active); + }, + + // Menus filter methods + _focusFilter: function() + { + this.element.find(this.js_selectors.menu_filter_input).trigger('focus'); + }, + /** + * Remove the current filter value and reset the menu nodes display + * @private + */ + _clearFiltering: function() + { + this.element.find(this.js_selectors.menu_filter_input).val(''); + + // Reset display of everything + // Note: We work on the 'display' property directly as there is a CSS rule managing the visibility of the active menu group + this.element.find('[data-role="ibo-navigation-menu--menu-nodes"]').css('display', ''); + this.element.find('[data-role="ibo-navigation-menu--menu-node"]').css('display', ''); + + // Mark menu as unfiltered + this.element.removeClass(this.css_classes.menu_filtered); + }, + /** + * Filter the displayed menu nodes regarding the current filter value + * @param sRawFilterValue string + * @private + */ + _doFiltering: function(sRawFilterValue) + { + const me = this; + const aFilterValueParts = this._formatValueForFilterComparison(sRawFilterValue).split(' '); + + // Mark menu as filtered + this.element.addClass(this.css_classes.menu_filtered); + + // Hide everything + this.element.find('[data-role="ibo-navigation-menu--menu-nodes"]').hide(); + this.element.find('[data-role="ibo-navigation-menu--menu-node"]').hide(); + + // Show matching menu node + this.element.find('[data-role="ibo-navigation-menu--menu-node"]').each(function(){ + const sNodeValue = me._formatValueForFilterComparison($(this).children('[data-role="ibo-navigation-menu--menu-node-title"]:first').text()); + let bMatches = true; + + // On first non matching part, we consider that the menu node is not a match + for(let iIdx in aFilterValueParts) + { + if(sNodeValue.indexOf(aFilterValueParts[iIdx]) === -1) + { + bMatches = false; + break; + } + } + + if(bMatches) + { + // Note: Selector must be recursive + $(this).parents('[data-role="ibo-navigation-menu--menu-nodes"], [data-role="ibo-navigation-menu--menu-node"]').show(); + $(this).show(); + } + }); + }, + /** + * Format sOriginalValue for an easier comparison (change accents, capitalized letters, ...) + * + * @param sOriginalValue string + * @returns string + * @private + */ + _formatValueForFilterComparison: function(sOriginalValue) + { + return sOriginalValue.toLowerCase().latinise(); } }); }); diff --git a/js/pages/backoffice.js b/js/pages/backoffice.js new file mode 100644 index 0000000000..4d19ff8c18 --- /dev/null +++ b/js/pages/backoffice.js @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013-2020 Combodo SARL + * + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + */ + +// Helpers +function ShowAboutBox() +{ + $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){ + $('body').append(data); + }); + return false; +} +function ArchiveMode(bEnable) +{ + var sPrevUrl = StripArchiveArgument(window.location.search); + if (bEnable) + { + window.location.search = sPrevUrl + '&with-archive=1'; + } + else + { + window.location.search = sPrevUrl + '&with-archive=0'; + } +} + +function StripArchiveArgument(sUrl) +{ + var res = sUrl.replace(/&with-archive=[01]/g, ''); + return res; +} + +// Processing +$(document).ready(function(){ + // Enable tooltips (abstraction layer between iTop markup and tooltip plugin to ease its replacement in the future) + // - Existing HTML markup, won't work on markup added dynamically after DOM ready (AJAX, ...) + $('[data-tooltip-content]').each(function(){ + const oOptions = {}; + + oOptions['content'] = $(this).attr('data-tooltip-content'); + oOptions['placement'] = $(this).attr('data-tooltip-placement') ?? 'top'; + oOptions['trigger'] = $(this).attr('data-tooltip-trigger') ?? 'mouseenter focus'; + + const sShiftingOffset = $(this).attr('data-tooltip-shifting-offset'); + const sDistanceOffset = $(this).attr('data-tooltip-distance-offset'); + oOptions['offset'] = [ + (sShiftingOffset === undefined) ? 0 : parseInt(sShiftingOffset), + (sDistanceOffset === undefined) ? 10 : parseInt(sDistanceOffset), + ]; + + oOptions['animation'] = $(this).attr('data-tooltip-animation') ?? 'shift-away-subtle'; + + const sShowDelay = $(this).attr('data-tooltip-show-delay'); + const sHideDelay = $(this).attr('data-tooltip-hide-delay'); + oOptions['delay'] = [ + (typeof sShowDelay === 'undefined') ? 200 : parseInt(sShowDelay), + (typeof sHideDelay === 'undefined') ? null : parseInt(sHideDelay), + ]; + + tippy(this, oOptions); + }); +}); \ No newline at end of file diff --git a/templates/layouts/navigation-menu/layout.html.twig b/templates/layouts/navigation-menu/layout.html.twig index 801c52ddb3..1198e41e73 100644 --- a/templates/layouts/navigation-menu/layout.html.twig +++ b/templates/layouts/navigation-menu/layout.html.twig @@ -1,10 +1,10 @@ -