Browse Source

N°762: Portal: New filter brick that pre-filters a Browse or Manage brick results from the home page.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4575 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 8 years ago
parent
commit
da1e9cfff2

+ 7 - 0
datamodels/2.x/itop-portal-base/cs.dict.itop-portal-base.php

@@ -121,3 +121,10 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
     'Brick:Portal:Create:Name' => 'Rychlé vytvoření',
     'Brick:Portal:Create:ChooseType' => 'Please, choose a type~~',
 ));
+
+// Filter brick
+Dict::Add('CS CZ', 'Czech', 'Čeština', array(
+    'Brick:Portal:Filter:Name' => 'Prefilter a brick~~',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg: install office~~',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Search~~',
+));

+ 7 - 0
datamodels/2.x/itop-portal-base/de.dict.itop-portal-base.php

@@ -116,3 +116,10 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
 	'Brick:Portal:Create:Name' => 'Schnelles Erstellen',
     'Brick:Portal:Create:ChooseType' => 'Please, choose a type~~',
 ));
+
+// Filter brick
+Dict::Add('DE DE', 'German', 'Deutsch', array(
+    'Brick:Portal:Filter:Name' => 'Prefilter a brick~~',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg: install office~~',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Search~~',
+));

+ 7 - 0
datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php

@@ -117,3 +117,10 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Brick:Portal:Create:Name' => 'Quick creation',
     'Brick:Portal:Create:ChooseType' => 'Please, choose a type',
 ));
+
+// Filter brick
+Dict::Add('EN US', 'English', 'English', array(
+    'Brick:Portal:Filter:Name' => 'Prefilter a brick',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg: install office',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Search',
+));

+ 7 - 0
datamodels/2.x/itop-portal-base/es_cr.dict.itop-portal-base.php

@@ -117,3 +117,10 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
 	'Brick:Portal:Create:Name' => 'Creación rápida',
     'Brick:Portal:Create:ChooseType' => 'Please, choose a type~~',
 ));
+
+// Filter brick
+Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
+    'Brick:Portal:Filter:Name' => 'Prefilter a brick~~',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg: install office~~',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Search~~',
+));

+ 7 - 0
datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php

@@ -118,3 +118,10 @@ Dict::Add('FR FR', 'French', 'Français', array(
     'Brick:Portal:Create:ChooseType' => 'Veuillez choisir le type',
 ));
 
+// Filter brick
+Dict::Add('FR FR', 'French', 'Français', array(
+    'Brick:Portal:Filter:Name' => 'Préfiltre une brique',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'ex : installation office',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Rechercher',
+));
+

+ 2 - 1
datamodels/2.x/itop-portal-base/portal/src/controllers/managebrickcontroller.class.inc.php

@@ -57,7 +57,7 @@ class ManageBrickController extends BrickController
 		$aQueries = array();
 
 		// Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
-		$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->query->get('sDataLoading') !== null) ? $oRequest->query->get('sDataLoading') : $oBrick->GetDataLoading() );
+		$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->get('sDataLoading') !== null) ? $oRequest->get('sDataLoading') : $oBrick->GetDataLoading() );
 		// Getting search value
 		$sSearchValue = $oRequest->get('sSearchValue', null);
 
@@ -465,6 +465,7 @@ class ManageBrickController extends BrickController
 				'aGroupingAreasData' => $aGroupingAreasData,
 				'sDateFormat' => AttributeDate::GetFormat()->ToMomentJS(),
 				'sDateTimeFormat' => AttributeDateTime::GetFormat()->ToMomentJS(),
+                'sSearchValue' => $sSearchValue,
 			);
 
 			$oResponse = $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);

+ 179 - 0
datamodels/2.x/itop-portal-base/portal/src/entities/filterbrick.class.inc.php

@@ -0,0 +1,179 @@
+<?php
+
+// Copyright (C) 2010-2015 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
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+namespace Combodo\iTop\Portal\Brick;
+
+use \Combodo\iTop\DesignElement;
+use \Combodo\iTop\Portal\Brick\PortalBrick;
+use \Combodo\iTop\Portal\Brick\BrowseBrick;
+
+/**
+ * Description of FilterBrick
+ * 
+ * @author Guillaume Lajarige
+ */
+class FilterBrick extends PortalBrick
+{
+	const DEFAULT_VISIBLE_NAVIGATION_MENU = false;
+	const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/filter/tile.html.twig';
+	const DEFAULT_TARGET_BRICK_CLASS = 'Combodo\\iTop\\Portal\\Brick\\BrowseBrick';
+	const DEFAULT_SEARCH_PLACEHOLDER_VALUE = 'Brick:Portal:Filter:SearchInput:Placeholder';
+	const DEFAULT_SEARCH_SUBMIT_LABEL = 'Brick:Portal:Filter:SearchInput:Submit';
+	const DEFAULT_SEARCH_SUBMIT_CLASS = '';
+
+	protected $sTargetBrickId;
+	protected $sTargetBrickClass;
+	protected $sTargetBrickTab;
+	protected $sSearchPlaceholderValue;
+	protected $sSearchSubmitLabel;
+	protected $sSearchSubmitClass;
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->sTargetBrickClass = static::DEFAULT_TARGET_BRICK_CLASS;
+		$this->sSearchPlaceholderValue = static::DEFAULT_SEARCH_PLACEHOLDER_VALUE;
+		$this->sSearchSubmitLabel = static::DEFAULT_SEARCH_SUBMIT_LABEL;
+		$this->sSearchSubmitClass = static::DEFAULT_SEARCH_SUBMIT_CLASS;
+	}
+
+	public function GetTargetBrickId()
+	{
+		return $this->sTargetBrickId;
+	}
+
+	public function GetTargetBrickClass()
+    {
+        return $this->sTargetBrickClass;
+    }
+
+	public function GetTargetBrickTab()
+	{
+		return $this->sTargetBrickTab;
+	}
+
+	public function GetSearchPlaceholderValue()
+	{
+		return $this->sSearchPlaceholderValue;
+	}
+
+	public function GetSearchSubmitLabel()
+	{
+		return $this->sSearchSubmitLabel;
+	}
+
+	public function GetSearchSubmitClass()
+	{
+		return $this->sSearchSubmitClass;
+	}
+
+	public function SetTargetBrickId($sTargetBrickId)
+	{
+		$this->sTargetBrickId = $sTargetBrickId;
+		return $this;
+	}
+
+	public function SetTargetBrickClass($sTargetBrickClass)
+    {
+        $this->sTargetBrickClass = $sTargetBrickClass;
+    }
+
+	public function SetTargetBrickTab($sTargetBrickTab)
+	{
+		$this->sTargetBrickTab = $sTargetBrickTab;
+		return $this;
+	}
+
+	public function SetSearchPlaceholderValue($sSearchPlaceholderValue)
+	{
+		$this->sSearchPlaceholderValue = $sSearchPlaceholderValue;
+		return $this;
+	}
+
+	public function SetSearchSubmitLabel($sSearchSubmitLabel)
+	{
+		$this->sSearchSubmitLabel = $sSearchSubmitLabel;
+		return $this;
+	}
+
+	public function SetSearchSubmitClass($sSearchSubmitClass)
+	{
+		$this->sSearchSubmitClass = $sSearchSubmitClass;
+		return $this;
+	}
+
+	/**
+	 * Load the brick's data from the xml passed as a ModuleDesignElement.
+	 * This is used to set all the brick attributes at once.
+	 *
+	 * @param \Combodo\iTop\DesignElement $oMDElement
+	 * @return BrowseBrick
+	 * @throws DOMFormatException
+	 */
+	public function LoadFromXml(DesignElement $oMDElement)
+	{
+		parent::LoadFromXml($oMDElement);
+
+		// Checking specific elements
+		foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
+		{
+			switch ($oBrickSubNode->nodeName)
+			{
+				case 'target_brick':
+					foreach ($oBrickSubNode->childNodes as $oTargetBrickNode)
+					{
+						switch ($oTargetBrickNode->nodeName)
+						{
+                            case 'id':
+                                $this->SetTargetBrickId($oTargetBrickNode->GetText());
+                                break;
+                            case 'type':
+                                $this->SetTargetBrickClass($oTargetBrickNode->GetText());
+                                break;
+							case 'tab':
+								$this->SetTargetBrickTab($oTargetBrickNode->GetText());
+								break;
+						}
+					}
+					break;
+				case 'search_placeholder_value':
+					$this->SetSearchPlaceholderValue($oBrickSubNode->GetText(static::DEFAULT_SEARCH_PLACEHOLDER_VALUE));
+					break;
+				case 'search_submit_label':
+					$this->SetSearchSubmitLabel($oBrickSubNode->GetText(static::DEFAULT_SEARCH_SUBMIT_LABEL));
+					break;
+				case 'search_submit_class':
+					$this->SetSearchSubmitClass($oBrickSubNode->GetText(static::DEFAULT_SEARCH_SUBMIT_CLASS));
+					break;
+			}
+		}
+
+		// Checking that the brick has at least a target brick id
+		if (($this->GetTargetBrickId() === null) || ($this->GetTargetBrickId() === ''))
+		{
+			throw new DOMFormatException('FilterBrick : Must have a target brick id', null, null, $oMDElement);
+		}
+
+		return $this;
+	}
+
+}
+
+?>

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/layout.html.twig

@@ -19,7 +19,7 @@
 {% endblock %}
 
 {% block pMainContentHolder%}
-	{% if iItemsCount > 0 %}
+	{% if iItemsCount > 0 or sSearchValue is not null %}
 		<div class="panel panel-default">
 			{% block bBrowseMainContent %}
 			{% endblock %}

+ 4 - 4
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_list.html.twig

@@ -24,9 +24,9 @@
 		var aKeyTimeoutFilteredKeys = [16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
 		
 		// Show a loader inside the table
-		var showTableLoader = function()
+		var showTableLoader = function(oElem)
 		{
-			$('#brick-content-table > tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
+			oElem.children('tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
 		};
 		// Columns definition for the table from the oLevelsProperties
 		var getColumnsDefinition = function()
@@ -210,7 +210,7 @@
 		
 		$(document).ready(function()
 		{
-			showTableLoader();
+			showTableLoader($('#brick-content-table'));
 			
 			// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
 			// We would just have to override / complete the necessary elements
@@ -301,7 +301,7 @@
 			$('#brick-content-table').on('processing.dt', function(event, settings, processing){
 				if(processing === true)
 				{
-					showTableLoader();
+					showTableLoader($(this));
 				}
 			});
 			

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_tree.html.twig

@@ -28,7 +28,7 @@
 			<label>{{ 'Portal:Datatables:Language:Search'|dict_s }}<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table" value="{{ sSearchValue }}"></label>
 		</div>
 	</div>
-	<ul class="list-group" id="brick_content_tree" data-level-id="L">
+	<ul class="list-group tree" id="brick_content_tree" data-level-id="L">
 	</ul>
 
 	<div id="brick_content_empty" class="text-center">

+ 37 - 0
datamodels/2.x/itop-portal-base/portal/src/views/bricks/filter/tile.html.twig

@@ -0,0 +1,37 @@
+{# itop-portal-base/portal/src/views/bricks/filter/tile.html.twig #}
+{# Filter brick tile layout #}
+
+{% if brick.GetTargetBrickClass == 'Combodo\\iTop\\Portal\\Brick\\BrowseBrick' %}
+    {% set sTargetBrickUrl = app['url_generator'].generate('p_browse_brick_mode', {'sBrickId': brick.GetTargetBrickId, 'sBrowseMode': brick.GetTargetBrickTab}) %}
+{% elseif brick.GetTargetBrickClass == 'Combodo\\iTop\\Portal\\Brick\\ManageBrick' %}
+    {% set sTargetBrickUrl = app['url_generator'].generate('p_manage_brick', {'sBrickId': brick.GetTargetBrickId, 'sGroupingTab': brick.GetTargetBrickTab}) %}
+{% endif %}
+
+<div class="col-xs-12 col-sm-{{ brick.GetWidth }}">
+	{% block pTileWrapper %}
+		<a href="#" class="tile tile-filter-brick" id="brick-{{ brick.GetId }}" data-brick-id="{{ brick.GetId }}">
+			<div class="tile_decoration">
+				<span class="icon {{ brick.GetDecorationClassHome }}"></span>
+
+			</div>
+			<div class="tile_body">
+				<div class="tile_title">{{ brick.GetTitleHome|dict_s }}</div>
+				{% if brick.HasDescription %}
+					<div class="tile_description">{{ brick.GetDescription|dict_s|raw }}</div>
+				{% endif %}
+				<div class="tile_filterbox">
+					<form method="post" action="{{ sTargetBrickUrl }}" class="form-inline">
+						<div class="form-group">
+							<input type="text" class="form-control" name="sSearchValue" placeholder="{{ brick.GetSearchPlaceholderValue|dict_s }}" />
+						</div>
+						<input type="hidden" name="sDataLoading" value="{{ constant('\\Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_LAZY') }}" />
+						<button type="submit" class="btn btn-primary">
+							<span class="{{ brick.GetSearchSubmitClass }}"></span>
+							{{ brick.GetSearchSubmitLabel|dict_s }}
+						</button>
+					</form>
+				</div>
+			</div>
+		</a>
+	{% endblock %}
+</div>

+ 26 - 4
datamodels/2.x/itop-portal-base/portal/src/views/bricks/manage/layout.html.twig

@@ -76,8 +76,13 @@
 				'{{ aAreaData.sId }}': {{ aAreaData.aItems|json_encode()|raw }},
 			{% endfor %}
 		};
-		
-		// Columns definition for the table from the columnsProperties
+
+        // Show a loader inside the table
+        var showTableLoader = function(oElem)
+        {
+            oElem.children('tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
+        };
+        // Columns definition for the table from the columnsProperties
 		var getColumnsDefinition = function(tableName)
 		{
 			var tableProperties = columnsProperties[tableName];
@@ -152,6 +157,8 @@
 		$(document).ready(function(){
 			{% for aAreaData in aGroupingAreasData %}
 				{% set sAreaId = aAreaData.sId %}
+
+            	showTableLoader( $('#table-{{ sAreaId }}') );
 				var oTable{{ sAreaId }} = $('#table-{{ sAreaId }}').DataTable( {
 					"language": {
 						"processing":	  "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
@@ -175,7 +182,7 @@
 					},
 					"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
 					"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::DEFAULT_COUNT_PER_PAGE_LIST') }},
-					"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"ri><"col-sm-6"p>>',
+					"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
 					"columns": getColumnsDefinition('{{ sAreaId }}'),
 					"order": [[0, "desc"]],
 					"rowCallback": function(oRow, oData){
@@ -226,6 +233,15 @@
 								d.iCountPerPage = d.length;
 								d.columns = null;
 								d.orders = null;
+
+                                {% if sSearchValue is not null %}
+									// Sets default filter value
+									if(d.draw === 1)
+									{
+                                        $('#table-{{ sAreaId }}_filter input').val('{{ sSearchValue }}');
+										d.search.value = $('#table-{{ sAreaId }}_filter input').val();
+									}
+                                {% endif %}
 								if(d.search.value)
 								{
 									d.sSearchValue = d.search.value;
@@ -234,7 +250,7 @@
 						}
 					{% endif %}
 				} );
-				
+
 				// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
 				// Also removes accents from search string
 				// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
@@ -245,6 +261,12 @@
 					oKeyTimeout = setTimeout(function() {
 						oTable{{ sAreaId }}.search(me.value.latinise()).draw();
 					}, iSearchThrottle);
+				});// Shows a loader in the table when processing
+				$('#table-{{ sAreaId }}').on('processing.dt', function(event, settings, processing){
+					if(processing === true)
+					{
+						showTableLoader($(this));
+					}
 				});
 			{% endfor %}
 		});

+ 3 - 0
datamodels/2.x/itop-portal-base/portal/web/css/bootstrap-theme-combodo.css

@@ -3295,6 +3295,9 @@ a.badge:hover, a.badge:focus {
   text-decoration: none;
   cursor: pointer;
 }
+.list-group .list-group-item:hover {
+  background-color: rgba(254, 223, 167, 0.1);
+}
 .list-group-item.active > .badge, .nav-pills > .active > a > .badge {
   color: #ea7d1e;
   background-color: #fff;

+ 3 - 0
datamodels/2.x/itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss

@@ -4110,6 +4110,9 @@ a.badge:focus {
     text-decoration: none;
     cursor: pointer
 }
+.list-group .list-group-item:hover{
+    background-color: rgba(254, 223, 167, 0.1);
+}
 .list-group-item.active>.badge,
 .nav-pills>.active>a>.badge {
     color: $brand-primary;

+ 32 - 1
datamodels/2.x/itop-portal-base/portal/web/css/portal.css

@@ -546,12 +546,13 @@ footer {
 /****************/
 #brick_content_tree {
   position: relative;
+  margin-top: 0px;
 }
 .list-group-item > .list-group-item-actions {
   /*display: none; Displaying actions only when hovering was not unanimous in the team */
   position: absolute;
   top: 10px;
-  right: 10px;
+  right: 15px;
 }
 .list-group-item:hover > .list-group-item-actions, .grid-group-item:hover > .grid-group-item-actions {
   display: block;
@@ -791,6 +792,36 @@ table .group-actions {
 /****************/
 /* - List mode  */
 /****************/
+/****************/
+/* Filter brick */
+/****************/
+.tile.tile-filter-brick .tile_filterbox .form-group:first-child {
+  width: 100%;
+}
+.tile.tile-filter-brick .tile_filterbox input[type="text"] {
+  width: 100%;
+}
+@media (max-width: 768px) {
+  .tile.tile-filter-brick .tile_filterbox .form-group:first-child {
+    margin-bottom: 5px;
+  }
+}
+@media (min-width: 768px) {
+  .tile.tile-filter-brick .tile_filterbox form {
+    display: table;
+  }
+  .tile.tile-filter-brick .tile_filterbox .form-group:first-child {
+    display: table-cell;
+  }
+  .tile.tile-filter-brick .tile_filterbox button[type="submit"] {
+    margin-left: 5px;
+  }
+}
+@media (min-width: 992px) {
+  .tile.tile-filter-brick .tile_filterbox .form-group:first-child {
+    display: table-cell;
+  }
+}
 /*********/
 /* Forms */
 /*********/

+ 33 - 1
datamodels/2.x/itop-portal-base/portal/web/css/portal.scss

@@ -572,13 +572,14 @@ footer{
 /****************/
 #brick_content_tree{
 	position: relative;
+	margin-top: 0px;
 }
 
 .list-group-item > .list-group-item-actions{
 	/*display: none; Displaying actions only when hovering was not unanimous in the team */
 	position: absolute;
 	top: 10px;
-	right: 10px;
+	right: 15px;
 }
 .list-group-item:hover > .list-group-item-actions,
 .grid-group-item:hover > .grid-group-item-actions{
@@ -854,6 +855,37 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 /* - List mode  */
 /****************/
 
+/****************/
+/* Filter brick */
+/****************/
+.tile.tile-filter-brick .tile_filterbox .form-group:first-child{
+	width: 100%;
+}
+.tile.tile-filter-brick .tile_filterbox input[type="text"]{
+	width: 100%;
+}
+@media (max-width: 768px){
+	.tile.tile-filter-brick .tile_filterbox .form-group:first-child{
+		margin-bottom: 5px;
+	}
+}
+@media (min-width: 768px){
+	.tile.tile-filter-brick .tile_filterbox form{
+		display: table;
+	}
+	.tile.tile-filter-brick .tile_filterbox .form-group:first-child{
+		display: table-cell;
+	}
+	.tile.tile-filter-brick .tile_filterbox button[type="submit"]{
+		margin-left: 5px;
+	}
+}
+@media (min-width: 992px)
+{
+	.tile.tile-filter-brick .tile_filterbox .form-group:first-child{
+		display: table-cell;
+	}
+}
 
 /*********/
 /* Forms */

+ 7 - 0
datamodels/2.x/itop-portal-base/ru.dict.itop-portal-base.php

@@ -99,3 +99,10 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
     'Brick:Portal:Create:Name' => 'Быстрое создание',
     'Brick:Portal:Create:ChooseType' => 'Please, choose a type~~',
 ));
+
+// Filter brick
+Dict::Add('RU RU', 'Russian', 'Русский', array(
+    'Brick:Portal:Filter:Name' => 'Prefilter a brick~~',
+    'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg: install office~~',
+    'Brick:Portal:Filter:SearchInput:Submit' => 'Search~~',
+));