release: collapsible social bar, tooltips, URL validation, blog image fix #144

Merged
jmiller merged 8 commits from dev into main 2026-06-21 02:41:22 +00:00
13 changed files with 186 additions and 52 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 01.00.00
# VERSION: 02.26.01
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+9 -3
View File
@@ -8,14 +8,20 @@
DEFGROUP: Joomla.Template.Site
INGROUP: MokoOnyx.Documentation
PATH: ./CHANGELOG.md
VERSION: 02.26.00
VERSION: 02.26.01
BRIEF: Changelog file documenting version history of MokoOnyx
-->
# Changelog — MokoOnyx (VERSION: 02.26.00)
# Changelog — MokoOnyx (VERSION: 02.26.01)
## [Unreleased]
## [02.26.00] --- 2026-06-20
### Added
- Collapsible floating social bar with toggle button and localStorage persistence
- Bootstrap tooltips on all social icon links showing platform name on hover
- URL validation on social platform fields (browser, Joomla server-side, and PHP layout logging)
### Fixed
- Moved footer social icons below footer-menu and footer module positions for correct display order
## [02.26.00] --- 2026-06-20
+3 -1
View File
@@ -12,7 +12,7 @@ A modern, lightweight Joomla site template built on Cassiopeia with Font Awesome
| | |
|---|---|
| **Type** | Joomla Site Template |
| **Version** | 02.07.00 |
| **Version** | 02.22.00 |
| **Joomla** | 5.x / 6.x |
| **PHP** | 8.1+ |
| **License** | GPL-3.0-or-later |
@@ -39,6 +39,7 @@ On install, MokoOnyx automatically migrates settings, content references, and cu
| **Table of Contents** | Automatic TOC generation for long articles |
| **GTM / GA4** | Google Tag Manager and Analytics integration with smart visitor detection (login status, user group, page type) |
| **Template Overrides** | Overrides for all core Joomla modules, Community Builder, and DPCalendar |
| **Social Icons** | Configurable social media links in topbar, footer, or collapsible floating sidebar with tooltips and URL validation |
| **Cassiopeia Base** | Minimal core overrides for maximum Joomla upgrade compatibility |
---
@@ -79,6 +80,7 @@ Key parameters include:
- **Google Tag Manager**: GTM container ID
- **GA4**: Measurement ID and tracking options
- **Table of Contents**: Auto-generate TOC for articles with heading threshold
- **Social Icons**: Display positions, icon style, and platform URLs
---
+1 -1
View File
@@ -10,7 +10,7 @@
INGROUP: MokoOnyx.Governance
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
FILE: SECURITY.md
VERSION: 02.26.00
VERSION: 02.26.01
BRIEF: Security policy and vulnerability reporting process for MokoOnyx.
PATH: /SECURITY.md
NOTE: This policy is process oriented and does not replace secure engineering practices.
+1 -1
View File
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /html/layouts/joomla/module/card.php
* VERSION: 02.26.00
* VERSION: 02.26.01
* BRIEF: Custom card module chrome — renders module titles for all modules
*/
@@ -11,7 +11,7 @@
* INGROUP: MokoOnyx.Layouts
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /src/html/layouts/mokoonyx/article-metadata.php
* VERSION: 02.26.00
* VERSION: 02.26.01
* BRIEF: Article metadata footer layout -- renders jcfields grouped by field group
*/
+33 -7
View File
@@ -52,13 +52,22 @@ $platforms = [
$active = [];
foreach ($platforms as $key => [$iconClass, $langKey]) {
$url = trim((string) $params->get('social_' . $key . '_url', ''));
if ($url !== '' && preg_match('#^(https?://|mailto:|/)#i', $url)) {
$active[] = [
'url' => $url,
'iconClass' => $iconClass,
'label' => Text::_($langKey),
];
if ($url === '') {
continue;
}
if (!preg_match('#^(https?://[^\s<>"]+|mailto:[^\s<>"]+|/[^\s<>"]*)$#i', $url)) {
\Joomla\CMS\Log\Log::add(
'MokoOnyx social: skipped invalid URL for "' . $key . '": ' . $url,
\Joomla\CMS\Log\Log::WARNING,
'template'
);
continue;
}
$active[] = [
'url' => $url,
'iconClass' => $iconClass,
'label' => Text::_($langKey),
];
}
if (empty($active)) {
@@ -84,6 +93,10 @@ if ($position === 'floating') {
$listClass .= ' moko-social-icons--floating-' . $floatingPos;
}
?>
<?php if ($position === 'floating') : ?>
<div class="moko-social-floating-wrap moko-social-floating-wrap--<?php echo htmlspecialchars($floatingPos, ENT_QUOTES, 'UTF-8'); ?>"
id="mokoSocialFloating">
<?php endif; ?>
<nav class="<?php echo $listClass; ?>" aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_NAV_LABEL'); ?>">
<ul>
<?php foreach ($active as $item) : ?>
@@ -91,10 +104,23 @@ if ($position === 'floating') {
<a href="<?php echo htmlspecialchars($item['url'], ENT_QUOTES, 'UTF-8'); ?>"
target="_blank"
rel="noopener noreferrer"
aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>">
aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>"
data-bs-toggle="tooltip"
data-bs-placement="<?php echo $position === 'floating' ? ($floatingPos === 'left' ? 'right' : 'left') : 'top'; ?>"
title="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>">
<span class="<?php echo htmlspecialchars($item['iconClass'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true"></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</nav>
<?php if ($position === 'floating') : ?>
<button type="button"
class="moko-social-floating-toggle"
id="mokoSocialFloatingToggle"
aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE'); ?>"
aria-expanded="true">
<span class="fa-solid fa-chevron-<?php echo $floatingPos === 'left' ? 'left' : 'right'; ?>" aria-hidden="true"></span>
</button>
</div>
<?php endif; ?>
+2 -1
View File
@@ -286,6 +286,7 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on."
TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right"
TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar"
TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Colour"
TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the colour scheme for social icons."
TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)"
@@ -321,7 +322,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us"
; ===== CSS Variables tab (social) =====
TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
+2 -1
View File
@@ -286,6 +286,7 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on."
TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right"
TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar"
TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Color"
TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the color scheme for social icons."
TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)"
@@ -321,7 +322,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us"
; ===== CSS Variables tab (social) =====
TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
+1 -1
View File
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: ./media/css/a11y-high-contrast.css
* VERSION: 02.26.00
* VERSION: 02.26.01
* BRIEF: High-contrast stylesheet for accessibility toolbar
*/
+67 -11
View File
@@ -23522,7 +23522,7 @@ font-size: 0.8125rem;
.blog-item .item-image img {
width: 100%;
height: 100%;
object-fit: cover;
object-fit: none;
}
.fa-brands {
@@ -23679,26 +23679,42 @@ font-size: 0.8125rem;
}
/* Position: floating — fixed sidebar, hidden on mobile */
.moko-social-icons--floating {
.moko-social-floating-wrap {
position: fixed;
top: 50%;
transform: translateY(-50%);
z-index: 1030;
display: flex;
align-items: center;
transition: transform 0.3s ease;
}
.moko-social-floating-wrap--left {
left: 0;
flex-direction: row;
}
.moko-social-floating-wrap--right {
right: 0;
flex-direction: row-reverse;
}
.moko-social-floating-wrap--left.is-collapsed {
transform: translateY(-50%) translateX(calc(-100% + 1.5rem));
}
.moko-social-floating-wrap--right.is-collapsed {
transform: translateY(-50%) translateX(calc(100% - 1.5rem));
}
.moko-social-icons--floating {
display: flex;
}
.moko-social-icons--floating ul {
flex-direction: column;
}
.moko-social-icons--floating-left {
left: 0;
}
.moko-social-icons--floating-right {
right: 0;
}
.moko-social-icons--floating-left a {
border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0;
}
@@ -23712,8 +23728,48 @@ font-size: 0.8125rem;
border-radius: inherit;
}
/* Floating toggle button */
.moko-social-floating-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 2.5rem;
padding: 0;
border: 1px solid var(--social-icon-bg, var(--border-color, hsl(0, 0%, 80%)));
background: var(--social-icon-bg, var(--body-bg, hsl(0, 0%, 100%)));
color: var(--social-icon-color, currentColor);
cursor: pointer;
font-size: 0.65rem;
transition: background 0.2s, color 0.2s;
flex-shrink: 0;
}
.moko-social-floating-wrap--left .moko-social-floating-toggle {
border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0;
border-left: 0;
}
.moko-social-floating-wrap--right .moko-social-floating-toggle {
border-radius: var(--social-icon-radius, 0.375rem) 0 0 var(--social-icon-radius, 0.375rem);
border-right: 0;
}
.moko-social-floating-toggle:hover {
background: var(--social-icon-hover-bg, var(--accent-color-primary, hsl(220, 70%, 50%)));
color: #fff;
}
.moko-social-floating-toggle .fa-solid {
transition: transform 0.3s ease;
}
.is-collapsed .moko-social-floating-toggle .fa-solid {
transform: rotate(180deg);
}
@media (max-width: 991.98px) {
.moko-social-icons--floating {
.moko-social-floating-wrap {
display: none;
}
}
+42
View File
@@ -789,6 +789,46 @@
});
}
// ========================================================================
// FLOATING SOCIAL BAR (collapsible)
// ========================================================================
var socialStorageKey = "moko-social-collapsed";
function initSocialFloating() {
var wrap = doc.getElementById("mokoSocialFloating");
var toggle = doc.getElementById("mokoSocialFloatingToggle");
if (!wrap || !toggle) return;
// Restore saved state
try {
if (localStorage.getItem(socialStorageKey) === "1") {
wrap.classList.add("is-collapsed");
toggle.setAttribute("aria-expanded", "false");
}
} catch (e) {}
toggle.addEventListener("click", function () {
var collapsed = wrap.classList.toggle("is-collapsed");
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
try { localStorage.setItem(socialStorageKey, collapsed ? "1" : "0"); } catch (e) {}
});
}
// ========================================================================
// SOCIAL ICON TOOLTIPS
// ========================================================================
function initSocialTooltips() {
var triggers = doc.querySelectorAll('.moko-social-icons [data-bs-toggle="tooltip"]');
if (!triggers.length) return;
// Bootstrap 5 tooltip init
if (typeof bootstrap !== "undefined" && bootstrap.Tooltip) {
triggers.forEach(function (el) {
new bootstrap.Tooltip(el, { trigger: "hover focus" });
});
}
}
/**
* Run all template JS initializations
*/
@@ -815,6 +855,8 @@
initBackTop();
initSearchToggle();
initSidebarAccordion();
initSocialFloating();
initSocialTooltips();
initVarCopy();
}
+23 -23
View File
@@ -35,7 +35,7 @@
</updateservers>
<dlid prefix="dlid=" suffix=""/>
<name>mokoonyx</name>
<version>02.26.00</version>
<version>02.26.01</version>
<scriptfile>script.php</scriptfile>
<creationDate>2026-05-16</creationDate>
<author>Jonathan Miller || Moko Consulting</author>
@@ -387,28 +387,28 @@
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
<field name="social_urls_note" type="note" description="TPL_MOKOONYX_SOCIAL_URLS_NOTE"
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
<field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" />
<field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" />
<field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" />
<field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" />
<field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" />
<field name="social_github_url" type="url" default="" label="GitHub" filter="url" />
<field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" />
<field name="social_threads_url" type="url" default="" label="Threads" filter="url" />
<field name="social_discord_url" type="url" default="" label="Discord" filter="url" />
<field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" />
<field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" />
<field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" />
<field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" />
<field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" />
<field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" />
<field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" />
<field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" />
<field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" />
<field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" />
<field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" />
<field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" />
<field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" />
<field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" validate="url" />
<field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" validate="url" />
<field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" validate="url" />
<field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" validate="url" />
<field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" validate="url" />
<field name="social_github_url" type="url" default="" label="GitHub" filter="url" validate="url" />
<field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" validate="url" />
<field name="social_threads_url" type="url" default="" label="Threads" filter="url" validate="url" />
<field name="social_discord_url" type="url" default="" label="Discord" filter="url" validate="url" />
<field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" validate="url" />
<field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" validate="url" />
<field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" validate="url" />
<field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" validate="url" />
<field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" validate="url" />
<field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" validate="url" />
<field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" validate="url" />
<field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" validate="url" />
<field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" validate="url" />
<field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" validate="url" />
<field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" validate="url" />
<field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" validate="url" />
<field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" validate="url" />
<field name="social_mail_url" type="url" default="" label="Email" description="TPL_MOKOONYX_SOCIAL_MAIL_DESC" filter="string" />
</fieldset>