mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-18 14:58:43 +02:00
N°2847 - Move dashboard title and menu to the top bar
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Component\Button\ButtonFactory;
|
||||
use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar;
|
||||
|
||||
require_once(APPROOT.'application/dashboardlayout.class.inc.php');
|
||||
require_once(APPROOT.'application/dashlet.class.inc.php');
|
||||
require_once(APPROOT.'core/modelreflection.class.inc.php');
|
||||
@@ -529,34 +532,37 @@ EOF
|
||||
*/
|
||||
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
|
||||
{
|
||||
if (!array_key_exists('dashboard_div_id', $aExtraParams))
|
||||
{
|
||||
if (!array_key_exists('dashboard_div_id', $aExtraParams)) {
|
||||
$aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier');
|
||||
}
|
||||
|
||||
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
|
||||
$oPage->add(<<<HTML
|
||||
<div class="dashboard-title-line">
|
||||
<div class="dashboard-title">{$sTitleForHTML}</div>
|
||||
</div>
|
||||
HTML
|
||||
);
|
||||
|
||||
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
|
||||
if ($oPage instanceof iTopWebPage) {
|
||||
$oTopBar = $oPage->GetTopBarLayout();
|
||||
$oToolbar = new Toolbar();
|
||||
$oTopBar->SetToolbar($oToolbar);
|
||||
$oToolbar->AddHtml($sHtml);
|
||||
} else {
|
||||
$oPage->add_script(<<<JS
|
||||
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML");
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/** @var \DashboardLayoutMultiCol $oLayout */
|
||||
$oLayout = new $this->sLayoutClass();
|
||||
|
||||
foreach($this->aCells as $iCellIdx => $aDashlets)
|
||||
{
|
||||
foreach($aDashlets as $oDashlet)
|
||||
{
|
||||
foreach ($this->aCells as $iCellIdx => $aDashlets) {
|
||||
foreach ($aDashlets as $oDashlet) {
|
||||
$aDashletCoordinates = $oLayout->GetDashletCoordinates($iCellIdx);
|
||||
$this->PrepareDashletForRendering($oDashlet, $aDashletCoordinates, $aExtraParams);
|
||||
}
|
||||
}
|
||||
|
||||
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
|
||||
if (!$bEditMode)
|
||||
{
|
||||
if (!$bEditMode) {
|
||||
$oPage->add_linked_script('../js/dashlet.js');
|
||||
$oPage->add_linked_script('../js/dashboard.js');
|
||||
}
|
||||
@@ -987,39 +993,38 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \iTopWebPage $oPage
|
||||
* @param WebPage $oPage
|
||||
* @param array $aAjaxParams
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
protected function RenderSelector($oPage, $aAjaxParams = array())
|
||||
protected function RenderSelector(WebPage $oPage, $aAjaxParams = array())
|
||||
{
|
||||
$sId = $this->GetId();
|
||||
$sDivId = utils::Sanitize($sId, '', 'element_identifier');
|
||||
$sExtraParams = json_encode($aAjaxParams);
|
||||
|
||||
$sSelectorHtml = '<div class="dashboard-selector">';
|
||||
if ($this->HasCustomDashboard())
|
||||
{
|
||||
$sSelectorHtml = '<div class="ibo-top-bar--toolbar-dashboard-selector">';
|
||||
if ($this->HasCustomDashboard()) {
|
||||
$bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sId, false);
|
||||
$sStandard = Dict::S('UI:Toggle:StandardDashboard');
|
||||
$sSelectorHtml .= '<div class="selector-label">'.$sStandard.'</div>';
|
||||
$sSelectorHtml .= '<label class="switch"><input type="checkbox" onchange="ToggleDashboardSelector'.$sDivId.'();" '.($bStandardSelected ? '' : 'checked').'><span class="slider round"></span></label></input></label>';
|
||||
$sCustom = Dict::S('UI:Toggle:CustomDashboard');
|
||||
$sSelectorHtml .= '<div class="selector-label">'.$sCustom.'</div>';
|
||||
|
||||
}
|
||||
$sSelectorHtml .= '</div>';
|
||||
$sSelectorHtml = addslashes($sSelectorHtml);
|
||||
$sFile = addslashes($this->GetDefinitionFile());
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.dashboard-title').after('$sSelectorHtml');
|
||||
EOF
|
||||
);
|
||||
if ($oPage instanceof iTopWebPage) {
|
||||
$oToolbar = $oPage->GetTopBarLayout()->GetToolbar();
|
||||
$oToolbar->AddHtml($sSelectorHtml);
|
||||
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
function ToggleDashboardSelector$sDivId()
|
||||
{
|
||||
$('.ibo-dashboard#$sDivId').block();
|
||||
@@ -1032,7 +1037,14 @@ EOF
|
||||
);
|
||||
}
|
||||
EOF
|
||||
);
|
||||
);
|
||||
} else {
|
||||
$sSelectorHtml = addslashes($sSelectorHtml);
|
||||
$oPage->add_script(<<<JS
|
||||
$(".ibo-top-bar--toolbar-dashboard-selector").replaceWith("$sSelectorHtml");
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1064,43 +1076,67 @@ EOF
|
||||
*/
|
||||
protected function RenderEditionTools(WebPage $oPage, $aExtraParams)
|
||||
{
|
||||
if (!($oPage instanceof iTopWebPage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
|
||||
$sEditMenu = "<div id=\"DashboardMenu\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-pencil-alt\"></i><ul>";
|
||||
|
||||
$sId = utils::Sanitize($this->GetId(), '', 'element_identifier');
|
||||
|
||||
$sMenuTogglerId = "ibo-dashboard-menu-toggler-{$sId}";
|
||||
$sPopoverMenuId = "ibo-dashboard-menu-popover-{$sId}";
|
||||
$sName = 'UI:Dashboard:Actions';
|
||||
$oToolbar = $oPage->GetTopBarLayout()->GetToolbar();
|
||||
$oActionButton = ButtonFactory::MakeLinkNeutral('', '', 'fas fa-ellipsis-v', $sName, '', $sMenuTogglerId);
|
||||
$oActionButton->AddCSSClasses("ibo-top-bar--toolbar-dashboard-menu-toggler");
|
||||
|
||||
$oToolbar->AddSubBlock($oActionButton);
|
||||
|
||||
$aActions = array();
|
||||
$sFile = addslashes($this->sDefinitionFile);
|
||||
$sJSExtraParams = json_encode($aExtraParams);
|
||||
$bCanEdit = true;
|
||||
if ($this->HasCustomDashboard())
|
||||
{
|
||||
if ($this->HasCustomDashboard()) {
|
||||
$bCanEdit = !appUserPreferences::GetPref('display_original_dashboard_'.$this->GetId(), false);
|
||||
}
|
||||
if ($bCanEdit)
|
||||
{
|
||||
if ($bCanEdit) {
|
||||
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
|
||||
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
|
||||
}
|
||||
|
||||
if ($this->bCustomized)
|
||||
{
|
||||
if ($this->bCustomized) {
|
||||
$oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'),
|
||||
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false");
|
||||
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false");
|
||||
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
|
||||
}
|
||||
|
||||
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
|
||||
$sEditMenu .= $oPage->RenderPopupMenuItems($aActions);
|
||||
$sEditMenu = addslashes($sEditMenu);
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.dashboard-title').after('$sEditMenu');
|
||||
$('#DashboardMenu>ul').popupmenu();
|
||||
|
||||
$oToolbar->AddSubBlock($oPage->GetPopoverMenu($sPopoverMenuId, $aActions));
|
||||
$oActionButton->AddCSSClasses('ibo-action-button')
|
||||
->SetJsCode(<<<JS
|
||||
$("#{$sPopoverMenuId}").popover_menu({toggler: "#{$sMenuTogglerId}"});
|
||||
$('#{$sMenuTogglerId}').on('click', function(oEvent) {
|
||||
var oEventTarget = $('#{$sMenuTogglerId}');
|
||||
var aEventTargetPos = oEventTarget.position();
|
||||
var popover = $("#{$sPopoverMenuId}");
|
||||
|
||||
EOF
|
||||
);
|
||||
popover.css({
|
||||
// 'top': (aEventTargetPos.top + parseInt(oEventTarget.css('marginTop'), 10) + oEventTarget.height()) + 'px',
|
||||
// 'left': (aEventTargetPos.left + parseInt(oEventTarget.css('marginLeft'), 10) + oEventTarget.width() - popover.width()) + 'px',
|
||||
'top': (aEventTargetPos.top + oEventTarget.outerHeight(true)) + 'px',
|
||||
'left': (aEventTargetPos.left + oEventTarget.outerWidth(true) - popover.width()) + 'px',
|
||||
'z-index': 10060
|
||||
});
|
||||
popover.popover_menu("togglePopup");
|
||||
});
|
||||
JS
|
||||
);
|
||||
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
function EditDashboard(sId, sDashboardFile, aExtraParams)
|
||||
{
|
||||
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'dashboard_editor', id: sId, file: sDashboardFile, extra_params: aExtraParams, reload_url: '$sReloadURL'},
|
||||
@@ -1584,12 +1620,10 @@ JS
|
||||
{
|
||||
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
|
||||
$aClassAliases = array();
|
||||
try{
|
||||
try {
|
||||
$oFilter = $oDashlet->GetDBSearch($aExtraParams);
|
||||
$aClassAliases = $oFilter->GetSelectedClasses();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
} catch (Exception $e) {
|
||||
//on error, return default value
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -44,16 +44,106 @@ $ibo-top-bar--quick-actions--margin-right: $ibo-top-bar--elements-spacing !defau
|
||||
background-color: var(--ibo-top-bar--background-color);
|
||||
@extend %ibo-elevation-100;
|
||||
|
||||
.ibo-breadcrumbs{
|
||||
.ibo-breadcrumbs {
|
||||
flex-grow: 1; /* Occupy as much width as possible */
|
||||
overflow-x: hidden; /* Avoid glitches when too many items */
|
||||
}
|
||||
}
|
||||
.ibo-top-bar--quick-actions{
|
||||
|
||||
.ibo-top-bar--quick-actions {
|
||||
@extend %ibo-full-height-content;
|
||||
margin-right: var(--ibo-top-bar--quick-actions--margin-right);
|
||||
|
||||
.ibo-global-search{
|
||||
.ibo-global-search {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-top-bar--toolbar {
|
||||
@extend %ibo-full-height-content;
|
||||
}
|
||||
|
||||
.ibo-top-bar--toolbar-dashboard-title {
|
||||
@extend %ibo-full-height-content;
|
||||
@extend %ibo-font-ral-med-250;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ibo-top-bar--toolbar-dashboard-menu-toggler {
|
||||
@extend %ibo-full-height-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ibo-top-bar--toolbar-dashboard-selector {
|
||||
@extend %ibo-full-height-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.selector-label {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
vertical-align: super;
|
||||
}
|
||||
}
|
||||
|
||||
// Round Toggle
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: $ibo-color-secondary-600;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: $ibo-color-secondary-300;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: $ibo-color-primary-600;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px $ibo-color-primary-600;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(14.5px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Combodo\iTop\Application\UI\Layout\TopBar;
|
||||
use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs;
|
||||
use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearch;
|
||||
use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreate;
|
||||
use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar;
|
||||
use Combodo\iTop\Application\UI\UIBlock;
|
||||
|
||||
/**
|
||||
@@ -45,6 +46,8 @@ class TopBar extends UIBlock
|
||||
protected $oGlobalSearch;
|
||||
/** @var Breadcrumbs|null $oBreadcrumbs */
|
||||
protected $oBreadcrumbs;
|
||||
/** @var Toolbar|null */
|
||||
protected $oToolbar;
|
||||
|
||||
/**
|
||||
* TopBar constructor.
|
||||
@@ -163,6 +166,35 @@ class TopBar extends UIBlock
|
||||
return ($this->oBreadcrumbs !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Toolbar|null
|
||||
*/
|
||||
public function GetToolbar(): ?Toolbar
|
||||
{
|
||||
return $this->oToolbar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Toolbar|null $oToolbar
|
||||
*
|
||||
* @return TopBar
|
||||
*/
|
||||
public function SetToolbar(?Toolbar $oToolbar): TopBar
|
||||
{
|
||||
$this->oToolbar = $oToolbar;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the breadcrumb has been set
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function HasToolbar()
|
||||
{
|
||||
return ($this->oToolbar !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -170,12 +202,10 @@ class TopBar extends UIBlock
|
||||
{
|
||||
$aSubBlocks = [];
|
||||
|
||||
$aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs'];
|
||||
foreach($aSubBlocksNames as $sSubBlockName)
|
||||
{
|
||||
$aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs', 'Toolbar'];
|
||||
foreach ($aSubBlocksNames as $sSubBlockName) {
|
||||
$sHasMethodName = 'Has'.$sSubBlockName;
|
||||
if(true === call_user_func_array([$this, $sHasMethodName], []))
|
||||
{
|
||||
if (true === call_user_func_array([$this, $sHasMethodName], [])) {
|
||||
$sPropertyName = 'o'.$sSubBlockName;
|
||||
$aSubBlocks[$this->$sPropertyName->GetId()] = $this->$sPropertyName;
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
|
||||
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
use Combodo\iTop\Application\UI\Component\Breadcrumbs\Breadcrumbs;
|
||||
use Combodo\iTop\Application\UI\Component\Panel\PanelFactory;
|
||||
use Combodo\iTop\Application\UI\iUIBlock;
|
||||
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
|
||||
use Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenuFactory;
|
||||
use Combodo\iTop\Application\UI\Layout\PageContent\PageContent;
|
||||
use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory;
|
||||
use Combodo\iTop\Application\UI\Layout\TopBar\TopBar;
|
||||
use Combodo\iTop\Application\UI\Layout\TopBar\TopBarFactory;
|
||||
use Combodo\iTop\Application\UI\UIBlock;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
@@ -49,6 +51,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
|
||||
/** @var \TabManager */
|
||||
protected $m_oTabs;
|
||||
protected $oTopBarLayout;
|
||||
protected $bBreadCrumbEnabled;
|
||||
protected $sBreadCrumbEntryId;
|
||||
protected $sBreadCrumbEntryLabel;
|
||||
@@ -78,16 +81,15 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
|
||||
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
|
||||
|
||||
if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST)))
|
||||
{
|
||||
if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST))) {
|
||||
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
|
||||
$this->bBreadCrumbEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
}
|
||||
|
||||
$this->SetTopBarLayout(TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry()));
|
||||
|
||||
utils::InitArchiveMode();
|
||||
|
||||
$this->m_aMessages = array();
|
||||
@@ -535,6 +537,8 @@ JS
|
||||
$this->sBreadCrumbEntryUrl = $sUrl;
|
||||
$this->sBreadCrumbEntryIcon = $sIcon;
|
||||
$this->sBreadCrumbEntryIconType = $sIconType;
|
||||
|
||||
$this->GetTopBarLayout()->SetBreadcrumbs(new Breadcrumbs($this->GetBreadCrumbsNewEntry(), Breadcrumbs::BLOCK_CODE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -632,20 +636,6 @@ JS
|
||||
return NavigationMenuFactory::MakeStandard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top bar layout (global search, breadcrumbs, ...)
|
||||
*
|
||||
* @internal
|
||||
* @return \Combodo\iTop\Application\UI\Layout\TopBar\TopBar
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function GetTopBarLayout()
|
||||
{
|
||||
return TopBarFactory::MakeStandard($this->GetBreadCrumbsNewEntry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content layout (main content, [side content,] manually added content, ...)
|
||||
* This function is public as the developer needs to be able to set how the content will be displayed.
|
||||
@@ -1393,4 +1383,25 @@ EOF
|
||||
$this->m_aInitScript[] = $sScript;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TopBar
|
||||
*/
|
||||
public function GetTopBarLayout(): TopBar
|
||||
{
|
||||
return $this->oTopBarLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TopBar $oTopBarLayout
|
||||
*
|
||||
* @return iTopWebPage
|
||||
*/
|
||||
public function SetTopBarLayout(TopBar $oTopBarLayout): iTopWebPage
|
||||
{
|
||||
$this->oTopBarLayout = $oTopBarLayout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,4 +10,9 @@
|
||||
{% if oUIBlock.HasBreadcrumbs() %}
|
||||
{{ render_block(oUIBlock.GetBreadcrumbs(), {aPage: aPage}) }}
|
||||
{% endif %}
|
||||
{% if oUIBlock.HasToolBar() %}
|
||||
<div class="ibo-top-bar--toolbar">
|
||||
{{ render_block(oUIBlock.GetToolBar(), {aPage: aPage}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
Reference in New Issue
Block a user