N°3207 - Global search: Introduce new widget

This commit is contained in:
Molkobain
2020-07-23 17:41:27 +02:00
parent 00dc1d3f3b
commit b3dcfea8dc
17 changed files with 531 additions and 9 deletions

View File

@@ -22,6 +22,7 @@ require_once(APPROOT."/application/applicationcontext.class.inc.php");
require_once(APPROOT."/application/user.preferences.class.inc.php");
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
/**
@@ -880,6 +881,7 @@ JS
$aData = [
'sId' => 'ibo-top-bar',
'aComponents' => [
'aGlobalSearch' => $this->GetGlobalSearchData(),
'aBreadCrumbs' => $this->GetBreadCrumbsData(),
],
];
@@ -887,6 +889,24 @@ JS
return $aData;
}
/**
* Return the global search data (last queries)
*
* @return array
* @throws \Exception
* @since 2.8.0
*/
protected function GetGlobalSearchData()
{
$aData = [
'sId' => 'ibo-global-search',
'sEndpoint' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=full_text',
'aLastQueries' => GlobalSearchHelper::GetLastQueries(),
];
return $aData;
}
/**
* Return the breadcrumbs data (iTop instance ID, new entry, ...)
*

View File

@@ -17,3 +17,4 @@
*/
@import "breadcrumbs";
@import "global-search";

View File

@@ -0,0 +1,200 @@
/*!
* 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
*/
/* SCSS variables */
$ibo-global-search--head--background-color: $ibo-color-white-100 !default;
$ibo-global-search--icon-padding-x: 16px !default;
$ibo-global-search--icon-padding-y: 0 !default;
$ibo-global-search--input--padding: 0 default;
$ibo-global-search--input--padding-x--is-opened: 8px !default;
$ibo-global-search--input--padding-y--is-opened: 8px !default;
$ibo-global-search--input--width: 0 !default;
$ibo-global-search--input--width--is-opened: 245px !default;
$ibo-global-search--input--text-color: $ibo-color-grey-800 !default;
$ibo-global-search--input--placeholder-color: $ibo-color-grey-600 !default;
$ibo-global-search--drawer--max-height: 200px !default;
$ibo-global-search--drawer--padding-x: $ibo-global-search--icon-padding-x !default;
$ibo-global-search--drawer--padding-y: 16px !default;
$ibo-global-search--drawer--top: -1 * ($ibo-global-search--drawer--max-height) !default;
$ibo-global-search--drawer--top--is-opened: 100% !default;
$ibo-global-search--drawer--background-color: $ibo-color-white-100 !default;
$ibo-global-search--compartment-title--margin-bottom: 8px !default;
$ibo-global-search--compartment-title--padding-left: 32px !default;
$ibo-global-search--compartment-title--text-color: $ibo-color-grey-600 !default;
$ibo-global-search--compartment-title--line-spacing: 8px !default;
$ibo-global-search--compartment-content--text-color: $ibo-color-grey-900 !default;
$ibo-global-search--compartment-element--margin-bottom: 8px !default;
$ibo-global-search--compartment-element-image--margin-right: 8px !default;
$ibo-global-search--compartment-element-image--width: 20px !default;
$ibo-global-search--compartment--placeholder-image--margin-top: 24px !default;
$ibo-global-search--compartment--placeholder-image--margin-bottom: 16px !default;
$ibo-global-search--compartment--placeholder-image--width: 66% !default;
$ibo-global-search--compartment--placeholder-hint--padding-x: 8px !default;
$ibo-global-search--compartment--placeholder-hint--padding-y: 0 !default;
$ibo-global-search--compartment--placeholder-hint--text-color: $ibo-color-grey-700 !default;
/* Animations*/
@keyframes ibo-global-search--drawer--opening {
from {
top: $ibo-global-search--drawer--top;
box-shadow: none;
}
to {
top: $ibo-global-search--drawer--top--is-opened;
box-shadow: $ibo-elevation-300;
}
}
/* SCSS rules */
.ibo-global-search{
position: relative;
@extend %ibo-vertically-centered-content;
&.ibo-global-search--is-opened{
.ibo-global-search--input{
padding: $ibo-global-search--input--padding-y--is-opened $ibo-global-search--input--padding-x--is-opened;
width: $ibo-global-search--input--width--is-opened;
}
.ibo-global-search--drawer{
animation-name: ibo-global-search--drawer--opening;
animation-delay: 0.1s;
animation-duration: 0.2s;
animation-direction: normal;
animation-fill-mode: forwards;
}
}
}
.ibo-global-search--head{
@extend %ibo-vertically-centered-content;
background-color: $ibo-global-search--head--background-color;
}
.ibo-global-search--icon{
align-self: center;
padding: $ibo-global-search--icon-padding-y $ibo-global-search--icon-padding-x;
@extend %ibo-font-ral-nor-400;
}
.ibo-global-search--input{
padding: $ibo-global-search--input--padding;
width: $ibo-global-search--input--width;
color: $ibo-global-search--input--text-color;
@extend %ibo-font-ral-nor-300;
border: none;
outline: none;
transition: all 0.2s ease-in-out;
&::placeholder{
color: $ibo-global-search--input--placeholder-color;
}
/* This rule is duplicated otherwise Chrome won't be able to parse it. */
&:-ms-input-placeholder,
&::-ms-input-placeholder{
color: $ibo-global-search--input--placeholder-color;
}
&:hover,
&:focus,
&:active{
@extend .ibo-global-search--input;
}
}
/* TODO: Make drawer appear below the top bar so its shadow is cast on the drawer */
.ibo-global-search--drawer{
z-index: -1;
position: absolute;
left: 0;
right: 0;
top: $ibo-global-search--drawer--top;
padding: $ibo-global-search--drawer--padding-y $ibo-global-search--drawer--padding-x;
background-color: $ibo-global-search--drawer--background-color;
box-shadow: none;
@extend %ibo-font-ral-nor-100;
}
.ibo-global-search--compartment-title{
margin-bottom: $ibo-global-search--compartment-title--margin-bottom;
padding-left: $ibo-global-search--compartment-title--padding-left;
overflow-x: hidden;
color: $ibo-global-search--compartment-title--text-color;
> span{
position: relative;
&::before,
&::after{
content: "";
display: inline-block;
position: absolute;
top: 50%;
height: 1px;
width: 600px;
border-top: 1px solid $ibo-global-search--compartment-title--text-color;
}
&::before{
right: 100%;
margin-right: $ibo-global-search--compartment-title--line-spacing;
}
&::after{
left: 100%;
margin-left: $ibo-global-search--compartment-title--line-spacing;
}
}
}
.ibo-global-search--compartment-content{
color: $ibo-global-search--compartment-content--text-color;
}
.ibo-global-search--compartment-element{
display: flex;
align-items: center;
color: inherit;
@extend %ibo-text-truncated-with-ellipsis;
&:not(:last-child){
margin-bottom: $ibo-global-search--compartment-element--margin-bottom;
}
}
.ibo-global-search--compartment-element-image{
margin-right: $ibo-global-search--compartment-element-image--margin-right;
width: $ibo-global-search--compartment-element-image--width;
}
.ibo-global-search--compartment--placeholder{
align-items: center;
display: flex;
flex-direction: column;
}
.ibo-global-search--compartment--placeholder-image{
width: $ibo-global-search--compartment--placeholder-image--width;
margin-top: $ibo-global-search--compartment--placeholder-image--margin-top;
margin-bottom: $ibo-global-search--compartment--placeholder-image--margin-bottom;
}
.ibo-global-search--compartment--placeholder-hint{
text-align: justify;
padding: $ibo-global-search--compartment--placeholder-hint--padding-y $ibo-global-search--compartment--placeholder-hint--padding-x;
color: $ibo-global-search--compartment--placeholder-hint--text-color;
@extend %ibo-font-ral-ita-100;
}

View File

@@ -48,4 +48,12 @@ $ibo-top-bar--quick-actions--margin-right: $ibo-top-bar--elements-spacing !defau
flex-grow: 1; /* Occupy as much width as possible */
overflow-x: hidden; /* Avoid glitches when too many items */
}
}
.ibo-top-bar--quick-actions{
@extend %ibo-vertically-centered-content;
margin-right: var(--ibo-top-bar--quick-actions--margin-right);
.ibo-global-search{
}
}

View File

@@ -19,4 +19,8 @@
// Global search
Dict::Add('EN US', 'English', 'English', array(
'UI:Component:GlobalSearch:Tooltip' => 'Search throughout the whole application',
'UI:Component:GlobalSearch:Input:Placeholder' => 'Search...',
'UI:Component:GlobalSearch:Recents:Title' => 'Recents',
'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'You haven\'t run any search yet',
));

View File

@@ -0,0 +1 @@
<svg id="e8ab07fb-aa66-4eb8-9149-b5abc1630568" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" width="799.70314" height="607.18766" viewBox="0 0 799.70314 607.18766"><rect x="0.27492" y="0.36501" width="643.86161" height="412.35762" fill="#e6e6e6"/><rect x="18.68599" y="52.08494" width="607.03946" height="336.24258" fill="#fff"/><rect width="643.86161" height="27.3536" fill="#6c63ff"/><circle cx="20.327" cy="13.98461" r="5.06978" fill="#fff"/><circle cx="39.57061" cy="13.98461" r="5.06978" fill="#fff"/><circle cx="58.81422" cy="13.98461" r="5.06978" fill="#fff"/><rect x="73.84385" y="86.97284" width="155.98054" height="266.46676" fill="#e6e6e6"/><rect x="256.74961" y="86.97284" width="129.98379" height="73.34799" fill="#6c63ff"/><rect x="256.74961" y="180.74686" width="129.98379" height="78.91873" fill="#e6e6e6"/><rect x="256.74961" y="280.09161" width="129.98379" height="73.34799" fill="#e6e6e6"/><rect x="414.58706" y="86.97284" width="155.98054" height="116.12476" fill="#e6e6e6"/><rect x="414.58706" y="237.31485" width="155.98054" height="116.12476" fill="#6c63ff"/><path d="M936.78726,353.0853c-10.28895-17.5356-30.64482-18.35283-30.64482-18.35283s-19.83563-2.55851-32.56006,24.14841c-11.86019,24.89305-28.22876,48.9278-2.63521,54.75508l4.62294-14.51317,2.863,15.59366a99.28429,99.28429,0,0,0,10.95053.18877c27.40862-.89257,53.5112.26114,52.67074-9.65934C940.93708,392.058,946.6873,369.95805,936.78726,353.0853Z" transform="translate(-200.14843 -146.40617)" fill="#2f2e41"/><path d="M839.21678,446.71189l-9,23-45,42s-27,20-15,26,23-22,23-22l48-29,10-33Z" transform="translate(-200.14843 -146.40617)" fill="#ffb8b8"/><polygon points="654.068 555.306 663.068 578.306 676.068 578.306 665.068 551.306 654.068 555.306" fill="#ffb8b8"/><polygon points="693.068 565.306 702.068 588.306 715.068 588.306 704.068 561.306 693.068 565.306" fill="#ffb8b8"/><path d="M892.21678,715.71189l17-4-22-89,7.30462-68.48066.19538-.01934s12.5-48.5,2.5-61.5-60-8-60-8-19,19-18,62,0,80,7,97,27,62,27,62l16-2-17-74-2.71808-64.32715c2.16314,25.77813,6.51709,74.65881,8.71808,78.32715C861.21678,648.71189,892.21678,715.71189,892.21678,715.71189Z" transform="translate(-200.14843 -146.40617)" fill="#2f2e41"/><path d="M943.21678,467.71189l7,25,31,56s27,24,16,27-28-22-28-22l-35-49-9-36Z" transform="translate(-200.14843 -146.40617)" fill="#ffb8b8"/><circle cx="707.06835" cy="225.30572" r="25" fill="#ffb8b8"/><path d="M894.21678,384.71189l-6,19-10,22,37-15s-2-16,0-20Z" transform="translate(-200.14843 -146.40617)" fill="#ffb8b8"/><path d="M937.21678,424.71189l-23-19-26,11,1.01872-16.226-27.74116,8.85981-4.27756,1.36615-6,31s-4,3-8,20a146.503,146.503,0,0,1-10,29s43-17,64,6c0,0,3-2,2-5s9-18,9-18,0,3,4-3,7-7,7-7Z" transform="translate(-200.14843 -146.40617)" fill="#6c63ff"/><path d="M863.21678,413.71189l-4.3679-3.52126s-3.6321-1.47874-6.6321,3.52126-18,36-18,36l18,9Z" transform="translate(-200.14843 -146.40617)" fill="#6c63ff"/><path d="M923.21678,423.71189l14,1s8,4,8,10,2,38,2,38l-22,7-9-28Z" transform="translate(-200.14843 -146.40617)" fill="#6c63ff"/><path d="M860.327,721.21189l-17,7s-20,1-17,11,33,3,33,3,27,1,27-6-4-15-8-15Z" transform="translate(-200.14843 -146.40617)" fill="#2f2e41"/><path d="M898.327,730.21189l-17,7s-20,1-17,11,33,3,33,3,27,1,27-6-4-15-8-15Z" transform="translate(-200.14843 -146.40617)" fill="#2f2e41"/><polygon points="732.09 204.989 711.979 194.363 684.208 198.71 678.462 224.307 692.765 223.753 696.761 214.348 696.761 223.598 703.361 223.342 707.191 208.37 709.585 224.307 733.048 223.824 732.09 204.989" fill="#2f2e41"/><path d="M797.72843,229.95254a135.01972,135.01972,0,1,0,7.65506,199.40281L971.00163,569.1031a12.44209,12.44209,0,1,0,16.04759-19.01834L821.43108,410.337A135.02731,135.02731,0,0,0,797.72843,229.95254ZM787.05171,396.88021a101.15766,101.15766,0,1,1-12.07656-142.54785A101.15764,101.15764,0,0,1,787.05171,396.88021Z" transform="translate(-200.14843 -146.40617)" fill="#3f3d56"/><path d="M644.50378,408.95686a101.16313,101.16313,0,0,1-17.16557-135.98894q-2.90123,2.92175-5.60938,6.1199A101.15766,101.15766,0,1,0,776.35328,409.55915q2.702-3.20223,5.089-6.559A101.163,101.163,0,0,1,644.50378,408.95686Z" transform="translate(-200.14843 -146.40617)" opacity="0.3"/></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,110 @@
/*
* 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
*/
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'breadcrumbs' the widget name
$.widget( 'itop.global_search',
{
// default options
options:
{
},
css_classes:
{
opened: 'ibo-global-search--is-opened',
},
js_selectors:
{
icon: '[data-role="ibo-global-search--icon"]',
form: '[data-role="ibo-global-search--head"]',
input: '[data-role="ibo-global-search--input"]',
compartment_element: '[data-role="ibo-global-search--compartment-element"]',
},
// the constructor
_create: function()
{
this.element.addClass('ibo-global-search');
this._bindEvents();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element.removeClass('ibo-global-search');
},
_bindEvents: function()
{
const me = this;
this.element.find(this.js_selectors.icon).on('click', function(oEvent){
me._onIconClick(oEvent);
});
this.element.find(this.js_selectors.form).on('submit', function(oEvent){
me._onFormSubmit(oEvent);
});
this.element.find(this.js_selectors.compartment_element).on('click', function(oEvent){
me._onCompartmentElementClick(oEvent, $(this));
});
$('body').on('click', function(oEvent){
me._onBodyClick(oEvent);
});
},
_onIconClick: function(oEvent)
{
// Avoid anchor glitch
oEvent.preventDefault();
// Open drawer
this.element.toggleClass(this.css_classes.opened);
// Focus in the input for a better UX
this.element.find(this.js_selectors.input).trigger('focus');
},
_onFormSubmit: function(oEvent)
{
const sSearchValue = this.element.find(this.js_selectors.input).val();
// Submit form only if something in the input
if(sSearchValue === '')
{
oEvent.preventDefault();
}
},
_onCompartmentElementClick: function(oEvent, oElementElem)
{
// Avoid anchor glitch
oEvent.preventDefault();
const sElementQuery = oElementElem.attr('data-query-raw');
this.element.find(this.js_selectors.input)
.val(sElementQuery)
.closest(this.js_selectors.form).trigger('submit');
},
_onBodyClick: function(oEvent)
{
if($(oEvent.target.closest('.ibo-global-search')).length === 0)
{
this.element.removeClass(this.css_classes.opened);
}
}
});
});

View File

@@ -138,6 +138,7 @@ return array(
'CheckStopWatchThresholds' => $baseDir . '/core/ormstopwatch.class.inc.php',
'CheckableExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\Application\\Branding' => $baseDir . '/sources/application/Branding.php',
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => $baseDir . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => $baseDir . '/sources/application/search/ajaxsearchexception.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => $baseDir . '/sources/application/search/criterionconversionabstract.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => $baseDir . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',

View File

@@ -368,6 +368,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'CheckStopWatchThresholds' => __DIR__ . '/../..' . '/core/ormstopwatch.class.inc.php',
'CheckableExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\Application\\Branding' => __DIR__ . '/../..' . '/sources/application/Branding.php',
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => __DIR__ . '/../..' . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => __DIR__ . '/../..' . '/sources/application/search/ajaxsearchexception.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => __DIR__ . '/../..' . '/sources/application/search/criterionconversionabstract.class.inc.php',
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => __DIR__ . '/../..' . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',

View File

@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
/**
* Displays a popup welcome message, once per session at maximum
* until the user unchecks the "Display welcome at startup"
@@ -546,15 +548,16 @@ try
case 'full_text': // Global "google-like" search
$oP->DisableBreadCrumb();
$sFullText = trim(utils::ReadParam('text', '', false, 'raw_data'));
$sQuery = trim(utils::ReadPostedParam('query', '', 'raw_data'));
$iTune = utils::ReadParam('tune', 0);
if (empty($sFullText))
if (empty($sQuery))
{
$oP->p(Dict::S('UI:Search:NoSearch'));
}
else
{
$iErrors = 0;
$sFullText = $sQuery;
// Check if a class name/label is supplied to limit the search
$sClassName = '';
@@ -581,6 +584,21 @@ try
$aFullTextNeedles = explode(' ', $sFullText);
}
// Save search to history
// - Prepare icon
$sQueryIconUrl = null;
if(!empty($sClassName))
{
$sQueryIconUrl = MetaModel::GetClassIcon($sClassName, false);
}
// - Prepare label
$sQueryLabel = null;
if($sQuery !== $sFullText)
{
$sQueryLabel = $sFullText;
}
GlobalSearchHelper::AddQueryToHistory($sQuery, $sQueryIconUrl, $sQueryLabel);
// Check the needle length
$iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min');
foreach ($aFullTextNeedles as $sNeedle)

View File

@@ -0,0 +1,117 @@
<?php
/**
* 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
*/
namespace Combodo\iTop\Application\GlobalSearch;
use appUserPreferences;
use utils;
/**
* Class GlobalSearchHelper
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Application\GlobalSearch
* @since 2.8.0
*/
class GlobalSearchHelper
{
const MAX_HISTORY_SIZE = 10;
const USER_PREF_CODE = 'global_search_history';
/**
* Add $sQuery to the history. History is limited to the static::MAX_HISTORY_SIZE last queries.
*
* @param string $sQuery Raw search query
* @param string|null $sIconRelUrl Relative URL of the icon
* @param string|null $sLabelAsHtml Alternate label for the query (eg. more human readable or with highlights), MUST be html entities otherwise there can be XSS flaws
*
* @return void
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function AddQueryToHistory($sQuery, $sIconRelUrl = null, $sLabelAsHtml = null)
{
$aNewQuery = [
'query' => $sQuery,
];
// Set icon only when necessary
if(!empty($sIconRelUrl))
{
//Ensure URL is relative to limit space in the preferences and avoid broken links in case app_root_url changes
$aNewQuery['icon_url'] = str_replace(utils::GetAbsoluteUrlAppRoot(), '', $sIconRelUrl);
}
// Set label only when necessary to avoid unnecessary space filling of the preferences in the DB
if(!empty($sLabelAsHtml))
{
$aNewQuery['label_html'] = $sLabelAsHtml;
}
/** @var array $aQueriesHistory */
$aQueriesHistory = appUserPreferences::GetPref(static::USER_PREF_CODE, []);
// Remove same query from history to avoid duplicates
for($iIdx = 0; $iIdx < count($aQueriesHistory); $iIdx++)
{
if($aQueriesHistory[$iIdx]['query'] === $sQuery)
{
unset($aQueriesHistory[$iIdx]);
}
}
// Add new query
array_unshift($aQueriesHistory, $aNewQuery);
// Truncate history
if(count($aQueriesHistory) > static::MAX_HISTORY_SIZE)
{
$aQueriesHistory = array_slice($aQueriesHistory, 0, static::MAX_HISTORY_SIZE);
}
appUserPreferences::SetPref(static::USER_PREF_CODE, $aQueriesHistory);
}
/**
* Return an array of pasted queries, including the query itself and its HTML label
*
* @return array
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function GetLastQueries()
{
/** @var array $aLastQueries */
$aLastQueries = appUserPreferences::GetPref(static::USER_PREF_CODE, []);
// Add HTML label if missing
for($iIdx = 0; $iIdx < count($aLastQueries); $iIdx++)
{
if(!isset($aLastQueries[$iIdx]['label_html']))
{
$aLastQueries[$iIdx]['label_html'] = utils::HtmlEntities($aLastQueries[$iIdx]['query']);
}
}
return $aLastQueries;
}
}

View File

@@ -0,0 +1,38 @@
<div id="{{ aGlobalSearch.sId }}" class="ibo-global-search" data-role="ibo-global-search">
<form action="{{ aGlobalSearch.sEndpoint }}" method="post" class="ibo-global-search--head" data-role="ibo-global-search--head">
<a href="#" class="ibo-global-search--icon fas fa-search" data-role="ibo-global-search--icon" data-tooltip-content="{{ 'UI:Component:GlobalSearch:Tooltip'|dict_s }}" data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
<input type="text" name="query" class="ibo-global-search--input" data-role="ibo-global-search--input" placeholder="{{ 'UI:Component:GlobalSearch:Input:Placeholder'|dict_s }}" />
</form>
<div class="ibo-global-search--drawer" data-role="ibo-global-search--drawer">
<div class="ibo-global-search--compartment">
<div class="ibo-global-search--compartment-title" data-role="ibo-global-search--compartment-title">
<span>{{ 'UI:Component:GlobalSearch:Recents:Title'|dict_s }}</span>
</div>
<div class="ibo-global-search--compartment-content" data-role="ibo-global-search--compartment-content">
{% if aGlobalSearch.aLastQueries|length > 0 %}
{% for aQuery in aGlobalSearch.aLastQueries %}
<a href="#" class="ibo-global-search--compartment-element" data-role="ibo-global-search--compartment-element" data-query-raw="{{ aQuery.query }}" title="{{ aQuery.query }}">
{% if aQuery.icon_url is defined %}
<img src="{{ aPage.sAbsoluteUrlAppRoot ~ aQuery.icon_url}}" class="ibo-global-search--compartment-element-image" />
{% endif %}
{{ aQuery.label_html|raw }}
</a>
{% endfor %}
{% else %}
<div class="ibo-global-search--compartment--placeholder">
<img class="ibo-global-search--compartment--placeholder-image" src="{{ aPage.sAbsoluteUrlAppRoot }}images/illustrations/undraw-collection/web_search.svg" />
<div class="ibo-global-search--compartment--placeholder-hint">{{ 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder'|dict_s }}</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{# TODO: Move this to a dedicated script file #}
<script type="text/javascript" src="../js/components/global-search.js"></script>
<script type="text/javascript">
setTimeout(function(){
$('#{{ aGlobalSearch.sId }}').global_search();
}, 500);
</script>

View File

@@ -14,7 +14,7 @@
</div>
<div class="ibo-navigation-menu--middle-part">
{% for aMenuGroup in aNavigationMenu.aMenuGroups %}
{{ include('layouts/navigation-menu/menu-group.html.twig', { aMenuGroup: aMenuGroup }, false) }}
{{ include('layouts/navigation-menu/menu-group.html.twig', { aMenuGroup: aMenuGroup }) }}
{% endfor %}
</div>
<div class="ibo-navigation-menu--bottom-part">
@@ -30,7 +30,7 @@
</div>
<div class="ibo-navigation-menu--menu-groups">
{% for aMenuGroup in aNavigationMenu.aMenuGroups %}
{{ include('layouts/navigation-menu/menu-nodes.html.twig', { aMenuGroup: aMenuGroup }, false) }}
{{ include('layouts/navigation-menu/menu-nodes.html.twig', { aMenuGroup: aMenuGroup }) }}
{% endfor %}
</div>
</div>

View File

@@ -8,7 +8,7 @@
{% if aMenuNode.aSubMenuNodes is defined and aMenuNode.aSubMenuNodes|length > 0 %}
<ul>
{% for aSubMenuNode in aMenuNode.aSubMenuNodes %}
{{ include('layouts/navigation-menu/menu-node.html.twig', { aMenuNode: aSubMenuNode }, false) }}
{{ include('layouts/navigation-menu/menu-node.html.twig', { aMenuNode: aSubMenuNode }) }}
{% endfor %}
</ul>
{% endif %}

View File

@@ -2,7 +2,7 @@
<h2 class="ibo-navigation-menu--menu-nodes-title">{{ aMenuGroup.sTitle }}</h2>
<ul>
{% for aMenuNode in aMenuGroup.aSubMenuNodes %}
{{ include('layouts/navigation-menu/menu-node.html.twig', { aMenuNode: aMenuNode }, false) }}
{{ include('layouts/navigation-menu/menu-node.html.twig', { aMenuNode: aMenuNode }) }}
{% endfor %}
</ul>
</div>

View File

@@ -1,3 +1,6 @@
<nav id="{{ aTopBar.sId }}" class="ibo-top-bar">
{{ include('components/breadcrumbs/layout.html.twig', { aBreadCrumbs: aTopBar.aComponents.aBreadCrumbs }, false) }}
<div class="ibo-top-bar--quick-actions">
{{ include('components/global-search/layout.html.twig', { aGlobalSearch: aTopBar.aComponents.aGlobalSearch }) }}
</div>
{{ include('components/breadcrumbs/layout.html.twig', { aBreadCrumbs: aTopBar.aComponents.aBreadCrumbs }) }}
</nav>

View File

@@ -38,9 +38,9 @@
{% endblock %}
</head>
<body data-gui-type="backoffice">
{{ include('layouts/navigation-menu/layout.html.twig', { aNavigationMenu: aLayouts.aNavigationMenu }, false) }}
{{ include('layouts/navigation-menu/layout.html.twig', { aNavigationMenu: aLayouts.aNavigationMenu }) }}
<div id="ibo-page-container">
{{ include('layouts/top-bar/layout.html.twig', { aTopBar: aLayouts.aTopBar }, false) }}
{{ include('layouts/top-bar/layout.html.twig', { aTopBar: aLayouts.aTopBar }) }}
<main id="ibo-page-content">
{{ aPage.aSanitizedContent|raw }}
</main>