feat(mokoonyx): tabbed profile/registration, drawer link-colour fix, retire right drawer #158

Merged
jmiller merged 2 commits from feature/profile-tabs-drawer-cleanup into dev 2026-07-16 17:48:31 +00:00
15 changed files with 360 additions and 85 deletions
+12
View File
@@ -15,6 +15,18 @@
# Changelog — MokoOnyx (VERSION: 02.28.09)
## [Unreleased]
### Added
- Tabbed user profile edit and registration screens: `com_users` fieldsets are grouped into Bootstrap tabs via the shared `layouts/mokoonyx/profile-tabset` layout (Account, Basic Settings, Passkey Login, Multi-factor Authentication, plus any plugin-contributed fieldsets such as MokoSuiteCRM / MokoSuiteCommunity)
### Changed
- Drawer menu links now follow the drawer's own foreground colour (`--offcanvas-color`) instead of the header navbar link colour, so they adapt to the active theme
### Fixed
- Drawer menu links were invisible (white-on-white) in the light theme; they now render correctly in both light and dark themes
### Removed
- Retired the right drawer entirely: the `drawer-right` template position, its toggle button, offcanvas markup, the `drawerRightIcon` parameter, and the associated CSS / JS / RTL rules. On update, any modules still assigned to `drawer-right` are automatically moved to the `sidebar-right` position
## [02.28.00] --- 2026-07-13
## [02.27.06] --- 2026-06-29
+1 -24
View File
@@ -36,7 +36,6 @@ $stickyHeader = (bool) $params->get('stickyHeader', 0);
// Drawer icon params (escaped)
$params_leftIcon = htmlspecialchars($params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_QUOTES, 'UTF-8');
$params_rightIcon = htmlspecialchars($params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_QUOTES, 'UTF-8');
// Template/Media path
$templatePath = 'media/templates/site/mokoonyx';
@@ -69,7 +68,7 @@ $wa->useScript('template.js');
$wa->useStyle('template.font.osaka');
// Smart Bootstrap component loading - only load what's needed
if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) {
if ($this->countModules('drawer-left', true)) {
// Load Bootstrap Offcanvas component for drawers
HTMLHelper::_('bootstrap.offcanvas');
}
@@ -295,16 +294,6 @@ $wa->useScript('user.js'); // js/user.js
</button>
<?php endif; ?>
<?php if ($this->countModules('drawer-right')) : ?>
<button class="drawer-toggle-right btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-right"
aria-controls="drawer-right">
<span class="<?php echo $params_rightIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
<div class="grid-child container-nav">
<?php if ($this->countModules('menu', true)) : ?>
@@ -430,18 +419,6 @@ $wa->useScript('user.js'); // js/user.js
</aside>
<?php endif; ?>
<?php if ($this->countModules('drawer-right', true)) : ?>
<!-- Right Offcanvas Drawer -->
<aside class="offcanvas offcanvas-end" tabindex="-1" id="drawer-right">
<div class="offcanvas-header">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-right" style="none" />
</div>
</aside>
<?php endif; ?>
<jdoc:include type="modules" name="debug" style="none" />
</body>
+78
View File
@@ -0,0 +1,78 @@
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
Template override of com_users profile edit: groups the form fieldsets into
Bootstrap tabs via layouts/mokoonyx/profile-tabset. Structure otherwise mirrors
the core template (save/cancel tasks, control fields, MFA block).
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
/** @var Joomla\Component\Users\Site\View\Profile\HtmlView $this */
HTMLHelper::_('bootstrap.tooltip', '.hasTooltip');
// Load user_profile plugin language
$lang = $this->getLanguage();
$lang->load('plg_user_profile', JPATH_ADMINISTRATOR);
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
// Multi-factor Authentication is rendered outside the form fieldsets by the core
// view, so pass it to the tab-set as an extra tab (only when the UI is present).
$extraTabs = [];
if ($this->mfaConfigurationUI) {
$extraTabs[] = [
'id' => 'mfa',
'title' => Text::_('COM_USERS_PROFILE_MULTIFACTOR_AUTH'),
'icon' => 'fa-solid fa-shield-halved',
'html' => $this->mfaConfigurationUI,
];
}
?>
<div class="com-users-profile__edit profile-edit">
<?php if ($this->params->get('show_page_heading')) : ?>
<div class="page-header">
<h1>
<?php echo $this->escape($this->params->get('page_heading')); ?>
</h1>
</div>
<?php endif; ?>
<form id="member-profile" action="<?php echo Route::_('index.php'); ?>" method="post" class="com-users-profile__edit-form form-validate form-horizontal well" enctype="multipart/form-data">
<?php echo LayoutHelper::render('mokoonyx.profile-tabset', [
'form' => $this->form,
'idBase' => 'profile',
'extraTabs' => $extraTabs,
]); ?>
<div class="com-users-profile__edit-submit control-group mt-4">
<div class="controls">
<button type="submit" class="btn btn-primary validate" name="task" value="profile.save">
<span class="icon-check" aria-hidden="true"></span>
<?php echo Text::_('JSAVE'); ?>
</button>
<button type="submit" class="btn btn-danger" name="task" value="profile.cancel" formnovalidate>
<span class="icon-times" aria-hidden="true"></span>
<?php echo Text::_('JCANCEL'); ?>
</button>
<input type="hidden" name="option" value="com_users">
</div>
</div>
<?php echo $this->form->renderControlFields(); ?>
</form>
</div>
@@ -0,0 +1,57 @@
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
Template override of com_users registration: groups the form fieldsets into
Bootstrap tabs via layouts/mokoonyx/profile-tabset. The captcha fieldset stays
outside the tabs (rendered just before submit), mirroring the core template.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\Users\Site\View\Registration\HtmlView $this */
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
?>
<div class="com-users-registration registration">
<?php if ($this->params->get('show_page_heading')) : ?>
<div class="page-header">
<h1><?php echo $this->escape($this->params->get('page_heading')); ?></h1>
</div>
<?php endif; ?>
<form id="member-registration" action="<?php echo Route::_('index.php?task=registration.register'); ?>" method="post" class="com-users-registration__form form-validate" enctype="multipart/form-data">
<?php // Captcha is rendered separately below, so keep it out of the tabs. ?>
<?php echo LayoutHelper::render('mokoonyx.profile-tabset', [
'form' => $this->form,
'idBase' => 'registration',
'skip' => $this->captchaEnabled ? ['captcha'] : [],
]); ?>
<?php if ($this->captchaEnabled) : ?>
<?php echo $this->form->renderFieldset('captcha'); ?>
<?php endif; ?>
<div class="com-users-registration__submit control-group mt-4">
<div class="controls">
<button type="submit" class="com-users-registration__register btn btn-primary validate">
<?php echo Text::_('JREGISTER'); ?>
</button>
<input type="hidden" name="option" value="com_users">
<input type="hidden" name="task" value="registration.register">
</div>
</div>
<?php echo $this->form->renderControlFields(); ?>
</form>
</div>
@@ -0,0 +1,135 @@
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/**
* Profile / registration tab-set layout.
*
* Renders a Joomla form's fieldsets as Bootstrap 5 tabs using the native
* `uitab` HTMLHelper (which also loads the bootstrap.tab script). Any fieldset
* contributed by a plugin — including MokoSuiteCRM / MokoSuiteCommunity user
* plugins — automatically becomes its own tab; nothing here is hardcoded to a
* specific field.
*
* Expected $displayData keys:
* 'form' => Joomla\CMS\Form\Form The bound form to render.
* 'idBase' => string Unique prefix for tab ids ('profile' | 'registration').
* 'skip' => string[] Fieldset names to exclude from tabs
* (caller renders them itself, e.g. 'captcha').
* 'extraTabs' => array[] Extra tabs appended after the fieldset tabs.
* Each: ['id' => string, 'title' => string,
* 'icon' => string, 'html' => string]. Used for
* content rendered outside the form (e.g. MFA).
*/
/** @var Joomla\CMS\Form\Form $form */
$form = $displayData['form'];
$idBase = $displayData['idBase'] ?? 'profile';
$skip = $displayData['skip'] ?? [];
$extraTabs = $displayData['extraTabs'] ?? [];
// Collect fieldsets that actually contain fields (minus any the caller skips).
$groups = [];
foreach ($form->getFieldsets() as $name => $fieldset) {
if (in_array($name, $skip, true)) {
continue;
}
if (count($form->getFieldset($name))) {
$groups[$name] = $fieldset;
}
}
// Per-fieldset presentation.
// icon => Font Awesome 7 class for the tab.
// title => OPTIONAL title override, used only when a fieldset has no label of
// its own (e.g. a label-less "core" fieldset). May be a language key
// or a literal string.
//
// A fieldset's own translated label always wins when present, so Joomla/plugin
// wording ("Basic Settings", "Passkey Login", …) is preserved. Add entries only
// to pin an icon or name a label-less fieldset — including MokoSuiteCRM /
// MokoSuiteCommunity sections.
$tabMeta = [
'core' => ['icon' => 'fa-solid fa-user', 'title' => 'TPL_MOKOONYX_PROFILE_TAB_ACCOUNT'],
'default' => ['icon' => 'fa-solid fa-user', 'title' => 'TPL_MOKOONYX_PROFILE_TAB_ACCOUNT'],
'settings' => ['icon' => 'fa-solid fa-gear'],
'params' => ['icon' => 'fa-solid fa-gear'],
'profile' => ['icon' => 'fa-solid fa-id-card'],
'passwordless' => ['icon' => 'fa-solid fa-fingerprint'],
'webauthn' => ['icon' => 'fa-solid fa-fingerprint'],
// ── MokoSuiteCRM / MokoSuiteCommunity fieldsets: add entries here ─────────
];
// Resolve a fieldset to [title, icon] with graceful fallbacks.
$resolve = static function (string $name, $fieldset) use ($tabMeta): array {
$meta = $tabMeta[strtolower($name)] ?? [];
$icon = $meta['icon'] ?? 'fa-solid fa-folder-open';
if (!empty($fieldset->label)) {
$title = Text::_($fieldset->label);
} elseif (!empty($meta['title'])) {
$title = Text::_($meta['title']);
} else {
$title = ucwords(str_replace(['_', '-'], ' ', $name));
}
return [$title, $icon];
};
// Build a uniform tab list: fieldset tabs first, then any extra tabs.
$tabs = [];
foreach ($groups as $name => $fieldset) {
[$title, $icon] = $resolve($name, $fieldset);
$tabs[] = [
'id' => $idBase . '-' . $name,
'title' => $title,
'icon' => $icon,
'html' => $form->renderFieldset($name),
];
}
foreach ($extraTabs as $extra) {
if (empty($extra['html'])) {
continue;
}
$tabs[] = [
'id' => $idBase . '-' . ($extra['id'] ?? 'extra'),
'title' => $extra['title'] ?? '',
'icon' => $extra['icon'] ?? 'fa-solid fa-folder-open',
'html' => $extra['html'],
];
}
// A single section doesn't warrant a tab strip — render it flat instead.
if (count($tabs) <= 1) {
foreach ($tabs as $tab) {
echo '<fieldset>' . $tab['html'] . '</fieldset>';
}
return;
}
echo HTMLHelper::_('uitab.startTabSet', $idBase, ['active' => $tabs[0]['id'], 'recall' => true]);
foreach ($tabs as $tab) {
$label = '<span class="' . $tab['icon'] . ' me-2" aria-hidden="true"></span>'
. htmlspecialchars($tab['title'], ENT_QUOTES, 'UTF-8');
echo HTMLHelper::_('uitab.addTab', $idBase, $tab['id'], $label);
echo $tab['html'];
echo HTMLHelper::_('uitab.endTab');
}
echo HTMLHelper::_('uitab.endTabSet');
+1 -25
View File
@@ -195,11 +195,10 @@ $hasClass = '';
if ($this->countModules('sidebar-left', true)) { $hasClass .= ' has-sidebar-left'; }
if ($this->countModules('sidebar-right', true)) { $hasClass .= ' has-sidebar-right'; }
if ($this->countModules('drawer-left', true)) { $hasClass .= ' has-drawer-left'; }
if ($this->countModules('drawer-right', true)) { $hasClass .= ' has-drawer-right'; }
// Smart Bootstrap component loading - only load what's needed
HTMLHelper::_('bootstrap.collapse');
if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) {
if ($this->countModules('drawer-left', true)) {
HTMLHelper::_('bootstrap.offcanvas');
}
@@ -252,7 +251,6 @@ if ($faKitCode !== '') {
}
}
$params_leftIcon = htmlspecialchars($this->params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_COMPAT, 'UTF-8');
$params_rightIcon = htmlspecialchars($this->params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_COMPAT, 'UTF-8');
// Load theme palette stylesheets — Joomla auto-serves .min when debug is off
$wa->useStyle('template.light.standard');
@@ -490,16 +488,6 @@ $wa->useScript('user.js'); // js/user.js
</button>
<?php endif; ?>
<?php if ($this->countModules('drawer-right')) : ?>
<button class="drawer-toggle-right btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-right"
aria-controls="drawer-right">
<span class="<?php echo $params_rightIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if (($this->countModules('menu', true) || $this->countModules('search', true)) && !$hideMenuHome) : ?>
<div class="grid-child container-nav">
<?php // Mobile: hamburger (left) + search icon (right) on one line ?>
@@ -633,18 +621,6 @@ endif; ?>
</aside>
<?php endif; ?>
<?php if ($this->countModules('drawer-right', true)) : ?>
<!-- Right Offcanvas Drawer -->
<aside class="offcanvas offcanvas-end" tabindex="-1" id="drawer-right">
<div class="offcanvas-header justify-content-start">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"><span class="fa fa-close"></span></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-right" style="none" />
</div>
</aside>
<?php endif; ?>
<jdoc:include type="modules" name="debug" style="none" />
</body>
+5 -2
View File
@@ -18,8 +18,6 @@ TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS"
TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')."
TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS"
TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')."
; ===== Favicon =====
TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon"
@@ -332,3 +330,8 @@ TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
JGLOBAL_OFFLINE="Offline"
; ===== User profile / registration tabs =====
TPL_MOKOONYX_PROFILE_TAB_ACCOUNT="Account"
TPL_MOKOONYX_PROFILE_TAB_SETTINGS="Settings"
TPL_MOKOONYX_PROFILE_TAB_PROFILE="Profile"
@@ -27,5 +27,4 @@ TPL_MOKOONYX_POSITION_TOP_A="Top-a"
TPL_MOKOONYX_POSITION_TOP_B="Top-b"
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
TPL_MOKOONYX_XML_DESCRIPTION="<p><img src=\"https://img.shields.io/gitea/v/release/MokoConsulting/MokoOnyx?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version\" alt=\"Version\" /> <img src=\"https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white\" alt=\"License\" /> <img src=\"https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white\" alt=\"Joomla\" /> <img src=\"https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white\" alt=\"PHP\" /></p> <h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
+5 -2
View File
@@ -18,8 +18,6 @@ TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS"
TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')."
TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS"
TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')."
; ===== Favicon =====
TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon"
@@ -332,3 +330,8 @@ TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
JGLOBAL_OFFLINE="Offline"
; ===== User profile / registration tabs =====
TPL_MOKOONYX_PROFILE_TAB_ACCOUNT="Account"
TPL_MOKOONYX_PROFILE_TAB_SETTINGS="Settings"
TPL_MOKOONYX_PROFILE_TAB_PROFILE="Profile"
@@ -27,5 +27,4 @@ TPL_MOKOONYX_POSITION_TOP_A="Top-a"
TPL_MOKOONYX_POSITION_TOP_B="Top-b"
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
TPL_MOKOONYX_XML_DESCRIPTION="<h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Color Themes</h4> <p> Starter palette files are included with the template. To create a custom color scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customize the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
-12
View File
@@ -33,18 +33,6 @@
padding-right: calc(var(--btn-padding-x)*0.5) !important;
}
[dir="rtl"] .drawer-toggle-right {
position: fixed;
top: 250px;
left: 0px;
z-index: 1050;
background-color: var(--nav-bg-color) !important;
color: var(--nav-text-color) !important;
padding-left: calc(var(--btn-padding-x)*0.5) !important;
padding-right: calc(var(--btn-padding-x)*0.5) !important;
}
[dir="rtl"] ol,
[dir="rtl"] ul {
+18 -15
View File
@@ -96,8 +96,7 @@ form {
color: var(--color-link, #224FAA);
}
.offcanvas-start,
.offcanvas-end {
.offcanvas-start {
width: 280px;
}
@@ -116,19 +115,23 @@ button.drawer-toggle-left,
border-bottom-left-radius: 0 !important;
}
button.drawer-toggle-right,
.drawer-toggle-right.btn {
position: fixed;
top: 50%;
right: 0px;
z-index: 1050;
background-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
color: var(--accent-color-text, #fff) !important;
border-color: var(--accent-color, var(--color-primary, #2a69b8)) !important;
padding-left: .5rem;
padding-right: .5rem;
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
/* Drawer menu links must follow the drawer's own foreground colour
(--offcanvas-color), not the header navbar's link colour, which is white to
sit on the coloured navbar. Without this, menu links render white-on-white in
the light theme (invisible) and only look right in dark by coincidence. */
.offcanvas .mod-menu a,
.offcanvas .mod-menu span,
.offcanvas .mod-menu button,
.offcanvas .mod-menu .nav-link,
.offcanvas .mod-menu .metismenu-item > a,
.offcanvas .mod-menu .metismenu-item > span {
color: var(--offcanvas-color, var(--body-color, #22262a)) !important;
}
.offcanvas .mod-menu a:hover,
.offcanvas .mod-menu a:focus,
.offcanvas .mod-menu .metismenu-item > a:hover {
color: var(--color-hover, var(--accent-color-primary, #3f8ff0)) !important;
}
hr {
+1 -1
View File
@@ -506,7 +506,7 @@
*/
function initDrawers() {
// Check if any drawer buttons exist before initializing
var hasDrawers = doc.querySelector(".drawer-toggle-left") || doc.querySelector(".drawer-toggle-right");
var hasDrawers = doc.querySelector(".drawer-toggle-left");
if (!hasDrawers) {
return; // No drawers, skip initialization
}
+47
View File
@@ -105,9 +105,56 @@ class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
$this->lockExtension();
}
if ($type === 'update') {
$this->migrateRightDrawerModules();
}
return true;
}
/**
* Migrate modules from the retired drawer-right position to sidebar-right.
*
* The drawer-right template position and its markup were removed. Rather
* than stranding modules still assigned to it (they would silently stop
* displaying), move them to the sidebar-right position so they remain
* visible. Published and unpublished site modules are both moved so nothing
* is lost. Runs on update and is naturally idempotent — once migrated, no
* rows match drawer-right on subsequent updates.
*/
private function migrateRightDrawerModules(): void
{
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__modules'))
->set($db->quoteName('position') . ' = ' . $db->quote('sidebar-right'))
->where($db->quoteName('position') . ' = ' . $db->quote('drawer-right'))
->where($db->quoteName('client_id') . ' = 0');
$db->setQuery($query)->execute();
$moved = $db->getAffectedRows();
if ($moved > 0) {
Factory::getApplication()->enqueueMessage(
sprintf(
'<strong>MokoOnyx: The right drawer has been retired.</strong> '
. '%d module(s) were moved from the removed <code>drawer-right</code> position to '
. '<code>sidebar-right</code>. Review their placement and adjust if needed.',
$moved
),
'notice'
);
$this->logMessage("Right drawer retired — moved {$moved} module(s) from drawer-right to sidebar-right.");
} else {
$this->logMessage('Right drawer retired — no modules were assigned to drawer-right.');
}
} catch (\Throwable $e) {
$this->logMessage('Right drawer module migration failed: ' . $e->getMessage(), 'warning');
}
}
/**
* Replace MokoCassiopeia references in article content and module content.
*/
-2
View File
@@ -92,7 +92,6 @@
<position>offline</position>
<position>offline-footer</position>
<position>drawer-left</position>
<position>drawer-right</position>
</positions>
<languages>
<language tag="en-GB">language/en-GB/tpl_mokoonyx.ini</language>
@@ -144,7 +143,6 @@
<!-- Drawers tab -->
<fieldset name="drawers" label="TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL">
<field name="drawerLeftIcon" type="text" default="fa-solid fa-chevron-right" label="TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL" description="TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC" filter="string" />
<field name="drawerRightIcon" type="text" default="fa-solid fa-chevron-left" label="TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL" description="TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC" filter="string" />
</fieldset>
<!-- THEME TAB (all style/theme settings grouped) -->