feat: single MokoSuite menu item with collapsible ecosystem children
Consolidates all Moko components under one top-level "MokoSuite" sidebar entry. Each component with subviews is a nested collapsible. Also: Help link keeps target=_blank but hides external-link icon. Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
This commit is contained in:
@@ -2,9 +2,8 @@
|
||||
/**
|
||||
* MokoSuiteClient Admin Sidebar Menu
|
||||
*
|
||||
* Each installed Moko component gets its own top-level collapsible section.
|
||||
* com_mokosuitehq is always pinned first. com_mokosuiteclient uses static views
|
||||
* as children. All other components auto-discover their submenu items.
|
||||
* Single "MokoSuite" top-level item with all Moko ecosystem components
|
||||
* as collapsible children underneath.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -122,7 +121,6 @@ try
|
||||
);
|
||||
$menuItems = $db->loadObjectList() ?: [];
|
||||
|
||||
// Load language files for discovered components
|
||||
$lang = Factory::getLanguage();
|
||||
$loadedLangs = [];
|
||||
foreach ($menuItems as $m)
|
||||
@@ -131,20 +129,16 @@ try
|
||||
{
|
||||
$lang->load($m->element . '.sys', JPATH_ADMINISTRATOR);
|
||||
$lang->load($m->element, JPATH_ADMINISTRATOR);
|
||||
|
||||
// Also try component-local language path (Joomla 5/6 pattern)
|
||||
$compLangPath = JPATH_ADMINISTRATOR . '/components/' . $m->element;
|
||||
if (is_dir($compLangPath . '/language'))
|
||||
{
|
||||
$lang->load($m->element . '.sys', $compLangPath);
|
||||
$lang->load($m->element, $compLangPath);
|
||||
}
|
||||
|
||||
$loadedLangs[$m->element] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Group: level 1 = component parent, level 2 = children
|
||||
foreach ($menuItems as $m)
|
||||
{
|
||||
if ((int) $m->level === 1)
|
||||
@@ -178,10 +172,7 @@ try
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Silent — menu works without auto-discovered components
|
||||
}
|
||||
catch (\Throwable $e) {}
|
||||
|
||||
// Override com_mokosuiteclient children with static views
|
||||
if (isset($mokoComponents['com_mokosuiteclient']))
|
||||
@@ -191,7 +182,6 @@ if (isset($mokoComponents['com_mokosuiteclient']))
|
||||
}
|
||||
else
|
||||
{
|
||||
// com_mokosuiteclient not in admin menu — add it manually
|
||||
$mokoComponents['com_mokosuiteclient'] = [
|
||||
'id' => 0,
|
||||
'title' => 'MokoSuite',
|
||||
@@ -209,9 +199,6 @@ $rest = [];
|
||||
|
||||
foreach ($mokoComponents as $key => $comp)
|
||||
{
|
||||
// Shorten display titles:
|
||||
// MokoSuiteClient → MokoSuite, MokoSuiteHQ → MokoHQ
|
||||
// Everything else: MokoSuiteBackup → Backup, MokoSuiteOpenGraph → OpenGraph
|
||||
if ($key === 'com_mokosuiteclient')
|
||||
{
|
||||
$comp['title'] = 'MokoSuite';
|
||||
@@ -225,35 +212,29 @@ foreach ($mokoComponents as $key => $comp)
|
||||
$comp['title'] = preg_replace('/^MokoSuite\s*/i', '', $comp['title']);
|
||||
}
|
||||
|
||||
if ($key === 'com_mokosuitehq')
|
||||
{
|
||||
$hq = $comp;
|
||||
}
|
||||
elseif ($key === 'com_mokosuiteclient')
|
||||
{
|
||||
$client = $comp;
|
||||
}
|
||||
else
|
||||
{
|
||||
$rest[$key] = $comp;
|
||||
}
|
||||
if ($key === 'com_mokosuitehq') { $hq = $comp; }
|
||||
elseif ($key === 'com_mokosuiteclient') { $client = $comp; }
|
||||
else { $rest[$key] = $comp; }
|
||||
}
|
||||
|
||||
usort($rest, fn($a, $b) => strcasecmp($a['title'], $b['title']));
|
||||
|
||||
$sorted = [];
|
||||
if ($hq !== null)
|
||||
if ($hq !== null) $sorted[] = $hq;
|
||||
if ($client !== null) $sorted[] = $client;
|
||||
foreach ($rest as $comp) $sorted[] = $comp;
|
||||
|
||||
// Is ANY Moko component active?
|
||||
$anyActive = false;
|
||||
foreach ($sorted as $comp)
|
||||
{
|
||||
$sorted[] = $hq;
|
||||
}
|
||||
if ($client !== null)
|
||||
{
|
||||
$sorted[] = $client;
|
||||
}
|
||||
foreach ($rest as $comp)
|
||||
{
|
||||
$sorted[] = $comp;
|
||||
$p = [];
|
||||
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $p);
|
||||
if (($p['option'] ?? '') === $currentOption) { $anyActive = true; break; }
|
||||
}
|
||||
if ($currentOption === 'com_plugins') $anyActive = true;
|
||||
|
||||
$iconStyle = 'display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;';
|
||||
?>
|
||||
|
||||
<style>
|
||||
@@ -261,58 +242,64 @@ foreach ($rest as $comp)
|
||||
</style>
|
||||
|
||||
<ul class="nav flex-column main-nav">
|
||||
<?php foreach ($sorted as $comp): ?>
|
||||
<?php
|
||||
$compParsed = [];
|
||||
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
|
||||
$compOption = $compParsed['option'] ?? '';
|
||||
$compActive = ($compOption === $currentOption);
|
||||
|
||||
// For com_mokosuiteclient static children, also check the plugins filter link
|
||||
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
|
||||
{
|
||||
$compActive = true;
|
||||
}
|
||||
|
||||
$hasChildren = !empty($comp['children']);
|
||||
$liClass = 'item mokosuiteclient-ext-item' . ($hasChildren ? ' parent item-level-1' : '') . ($compActive ? ' mm-active' : '');
|
||||
$aClass = ($hasChildren ? 'has-arrow' : 'no-dropdown') . ($compActive ? ' mm-active' : '');
|
||||
$childCollapse = 'collapse-level-1 mm-collapse' . ($compActive ? ' mm-show' : '');
|
||||
?>
|
||||
<li class="<?php echo $liClass; ?>">
|
||||
<a class="<?php echo $aClass; ?>" href="<?php echo $hasChildren ? '#' : Route::_($comp['link']); ?>"<?php echo ($compActive && !$hasChildren) ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
||||
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
|
||||
<li class="item parent item-level-1 mokosuiteclient-ext-item<?php echo $anyActive ? ' mm-active' : ''; ?>">
|
||||
<a class="has-arrow<?php echo $anyActive ? ' mm-active' : ''; ?>" href="#">
|
||||
<span class="icon-shield-alt" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
||||
<span class="sidebar-item-title">MokoSuite</span>
|
||||
</a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<ul class="<?php echo $childCollapse; ?>" style="padding-inline-start:0.5rem;">
|
||||
<?php foreach ($comp['children'] as $child): ?>
|
||||
<ul class="collapse-level-1 mm-collapse<?php echo $anyActive ? ' mm-show' : ''; ?>" style="padding-inline-start:0.5rem;">
|
||||
<?php foreach ($sorted as $comp): ?>
|
||||
<?php
|
||||
$childParsed = [];
|
||||
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
|
||||
$childOption = $childParsed['option'] ?? '';
|
||||
$childView = $childParsed['view'] ?? '';
|
||||
|
||||
$childActive = false;
|
||||
if ($childOption === $currentOption)
|
||||
$compParsed = [];
|
||||
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
|
||||
$compOption = $compParsed['option'] ?? '';
|
||||
$compActive = ($compOption === $currentOption);
|
||||
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
|
||||
{
|
||||
$childActive = empty($childView)
|
||||
? ($currentView === '' || $currentView === 'dashboard')
|
||||
: ($currentView === $childView);
|
||||
$compActive = true;
|
||||
}
|
||||
|
||||
$childLiClass = 'item mokosuiteclient-ext-child' . ($childActive ? ' mm-active' : '');
|
||||
$childAClass = 'no-dropdown' . ($childActive ? ' mm-active' : '');
|
||||
$hasChildren = !empty($comp['children']);
|
||||
?>
|
||||
<li class="<?php echo $childLiClass; ?>">
|
||||
<a class="<?php echo $childAClass; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $child['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
||||
<span class="sidebar-item-title"><?php echo $child['title']; ?></span>
|
||||
<?php if ($hasChildren): ?>
|
||||
<li class="item parent item-level-2 mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
|
||||
<a class="has-arrow<?php echo $compActive ? ' mm-active' : ''; ?>" href="#">
|
||||
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
||||
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
|
||||
</a>
|
||||
<ul class="collapse-level-2 mm-collapse<?php echo $compActive ? ' mm-show' : ''; ?>" style="padding-inline-start:0.5rem;">
|
||||
<?php foreach ($comp['children'] as $child): ?>
|
||||
<?php
|
||||
$childParsed = [];
|
||||
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
|
||||
$childOption = $childParsed['option'] ?? '';
|
||||
$childView = $childParsed['view'] ?? '';
|
||||
$childActive = false;
|
||||
if ($childOption === $currentOption)
|
||||
{
|
||||
$childActive = empty($childView)
|
||||
? ($currentView === '' || $currentView === 'dashboard')
|
||||
: ($currentView === $childView);
|
||||
}
|
||||
?>
|
||||
<li class="item mokosuiteclient-ext-child<?php echo $childActive ? ' mm-active' : ''; ?>">
|
||||
<a class="no-dropdown<?php echo $childActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $child['icon']; ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
||||
<span class="sidebar-item-title"><?php echo $child['title']; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="item mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
|
||||
<a class="no-dropdown<?php echo $compActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($comp['link']); ?>"<?php echo $compActive ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
|
||||
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
@@ -383,12 +383,21 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
|
||||
var url = " . json_encode($supportUrl) . ";
|
||||
document.querySelectorAll('a[href*=\"help.joomla.org\"], a[href*=\"docs.joomla.org\"]').forEach(function(link) {
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
var extIcon = link.querySelector('.icon-external-link-alt, .icon-external-link, .fa-external-link-alt, .fa-up-right-from-square');
|
||||
if (extIcon) extIcon.remove();
|
||||
});
|
||||
document.querySelectorAll('a[href*=\"dashboard=help\"]').forEach(function(link) {
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
var extIcon = link.querySelector('.icon-external-link-alt, .icon-external-link, .fa-external-link-alt, .fa-up-right-from-square');
|
||||
if (extIcon) extIcon.remove();
|
||||
});
|
||||
});
|
||||
");
|
||||
$doc->addStyleDeclaration('a[href=\"' . $supportUrl . '\"] .icon-external-link-alt, a[href=\"' . $supportUrl . '\"] .fa-up-right-from-square { display: none !important; }');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user